Using Google Analytics Measurement Protocol with C++/Qt - A Software Engineering Exercise - Part 2
As I wrote in the previous article of this series, one of my goals with the Qt Google Analytics library for C++/Qt is to practice test driven development. From my perspective this is going to be a very educating and thus entertaining part of this whole undertaking. After all, unit testing in C++ can be difficult since the language can be so low level that it gets difficult to find suitable mock objects or stubs. And as if that would not be a challenge enough by itself, then it is a library that is going to communicate with a server on the other side of the internet. A server that is completely black box to us.
Normally this would make it pretty hard to write an automated test for. Don’t get me wrong, I’ve written such tests at CCP where we’d fire up a local webserver instance, or monkeypatch the relevant Python function so that expected responses for a given test request were returned. But this is either relatively slow since it requires starting and waiting for an external process, or it is very difficult to achieve because C++ is not a dynamic language like Python.
However, we can exploit some Qt and Google API specifics here to make our life a lot easier. For a start, the Google API does not send any response codes and just ignore malformed requests, which means that our library does not have to handle error responses - simply because it cannot - and thus sending expected responses is impossible. Additionally, it is considered good practice in Qt to only have a single QNetworkAccessManager
instance in your application, and since that class is the base for HTTP based network communication in Qt then our library needs to be able to use an external instance of it. Hooray, there is our requirement to provide the library with an API that might as well take a mock object derived QNetworkAccessManager
! Conveniently that Qt class also contains a protected virtual function that processes a QNetworkRequest
, the method via which it was sent and its associated payload to constructs a corresponding QNetworkReply
object. That makes it a gold mine right there: All we need to provision is QNetworkAccessManager
subclass where we override the createRequest()
method and check our expectations from within there. Look how simple this gets to validate a request our library is going to send:
QNetworkReply* TestNetworkAccessManager::createRequest( QNetworkAccessManager::Operation op, const QNetworkRequest& request, QIODevice *outgoingData )
{
// Url, headers or metadata do not match
m_failed = ( request != *m_expectedRequest );
// payload mismatch
if ( outgoingData )
{
QByteArray outgoing = outgoingData->readAll();
m_failed |= ( outgoing != m_expectedData );
}
// operation mismatch
m_failed |= ( op != m_expectedOp );
return QNetworkAccessManager::createRequest( op, request, outgoingData );
}
Our test can then simply check the failed flag to indicate success or failure. There is some obvious room for improvement - for example this method could write out a helpful message indicating where exactly the test failed. But overall this appears to me as a very elegant, to the point solution to the problem of verifying that the library is sending requests in a format that the Google Analytics backend expects.
Next up I should probably talk about some of the design choices I took or am taking for the library, and lay out my reasoning behind them, so that will be the content for part 3.