Getting started with Google Test
Jun 3, 2018
8 minute read

Unit testing in C++

I cut my teeth with TDD working in C# and taking advantage of the variety of really nice unit testing and mocking tools available within the cosy Microsoft playground. But in the last few years I’ve found myself working with C++ a lot more, and I really wanted to ensure that the unit testing experience was, as far as possible, as convenient and powerful.

Unit testing can be a little dry at times (what!? you mean you don’t get a rush from each incremental increase in the automated verifiability of correctness and modularity of your code?), so in particular when trying to teach and express the value of unit testing to an inexperienced team, it’s essential that the framework doesn’t make the task any slower or more difficult.

Thankfully, the good people down at Google have done some fantastic work in this area and created the Google Test framework, which I’m going to refer to as gtest from here on. I thought it might be worth my documenting my some examples and experiences I’ve had using this framework.

Two things to note:

  • This is not really meant to be a tutorial. It’s more a demonstration of how to use abstract unit-testing concepts in the gtest framework. To get much out of it, you’ll need to be familiar with unit testing, mocking, dependency injection, etc.

  • If you are looking for a tutorial, Google’s own documentation is pretty good. A very useful primer is available on the project github page here, and similarly useful is a cheatsheet for syntactic reference.

  • I also happen to be using the Qt framework in these examples, so you’ll notice a few Qt core classes e.g. <QString>. This shouldn’t detract too much from the general approach - there’s nothing reliant on Qt here, it’s just basic stuff. And if you’re playing with the code I expect you should be able to swap out QString for a std:string without too much difficulty.


An example project

To give some context, in this example, I’m working on an application that displays a series of screens in a wizard-like system. One of these screens allows the user to apply localisation settings, and the design wireframes show the user selecting a locale from a list which is then used to update the system configuration.

Classes in this project are structured along the lines of MVC, with views calling methods on controllers (such as onPageDisplayed and pageCompleted), and the controllers interacting with underlying models. I’m going to use a class called LocalisationScreenController for an example, and test it using TestLocalisationScreenController

Setup

I’m not going to go into more detail on the structure of my project because this is a tad complex and very specific to my setup (git submodules etc). But if you’re getting started with gtest, predictably the key to setup is that you are going to need to link to the gtest libraries, `#include’ the appropriate header(s) and lift the boilerplate code from the primer in the framework documentation.

The class being tested

The class we’re testing has a signature that looks like this:

class LocalisationScreenController : public ScreenControllerBase
{
public:

    LocalisationScreenController(IScreen* screen, ILocalisationModel* localisationModel);

protected:

    virtual void onShow();
    virtual void onComplete();

private:

    void setupLocaleSelection();
    void setSystemLocale();

    ILocalisationModel* m_localisationModel;
};

The two injected dependencies are IScreen* which is the view for this controller, and ILocalisationModel* which is a model for managing localisation. Yes! This is a nice and straight-forward example. The ILocalisationModel* is stored as a member of the class, the IScreen* is actually stored as a member of the base class, which otherwise isn’t of much relevance here.

The test fixture

I’m going to focus on a couple of unit tests that exercise the onShow method, which is called by the framework I’ve created when the user arrives at this page. So let’s look at what the unit test class looks like:

#include "gtest/gtest.h"
#include "localisation_screen_controller.h"
#include "mock_localisation_model.h"
#include "mock_screen.h"

namespace
{
    class TestLocalisationScreenController : public Test
    {
    protected:
        TestLocalisationScreenController() {}

        virtual ~TestLocalisationScreenController() {}

        virtual void SetUp()
        {
            m_mockScreen = new NiceMock<MockScreen>();
            m_mockLocalisationModel = new NiceMock<MockLocalisationModel>();
            m_localisationScreenController = new LocalisationScreenController(m_mockScreen, m_mockLocalisationModel);
        }

        virtual void TearDown()
        {
            delete m_localisationScreenController;
            delete m_mockLocalisationModel;
            delete m_mockScreen;
        }

        NiceMock<MockScreen>* m_mockScreen;
        NiceMock<MockLocalisationModel>* m_mockLocalisationModel;
        LocalisationScreenController* m_localisationScreenController;
    };

    TEST_F(TestLocalisationScreenController, onShow_setsLocaleModel)
    {
        // To follow
    }

    TEST_F(TestLocalisationScreenController, onShow_setsCurrentlySelectedLocale)
    {
        // To follow
    }

A few things to cover here:

  • Strangely, gtest test fixtures use the base class Test, but I’m not complaining too much.

  • I’m using SetUp and TearDown overrides to create the object being tested and the mocks. These methods are run before and after each test (rather than before and after the whole fixture)

  • Mocks are created as a templated class called NiceMock<T>. There are three types of mocks in gtest, NiceMock, NaggyMock and StrictMock. The differences between these are described here.

Before we go any further, we need to look at how mocks work in gtest.

Mocks

While mocks in gtest are not “configuration-free”, they are impressively light given the amount of magic that is happening under the hood. Each mock needs to be defined along the lines of the following example, which is my mock for the Screen class. You can see IScreen is a view with typical methods to show and hide it, and to set and get properties from the UI.

#include "screen.h"
#include "gmock/gmock.h"

class MockScreen : public IScreen
{
public:
    MOCK_METHOD0(show, void());
    MOCK_METHOD0(hide, void());
    MOCK_METHOD2(getPropertyString, QString(const QString&, const QString&));
    MOCK_METHOD2(getPropertyInt, int(const QString&, const QString&));
    MOCK_METHOD2(getPropertyBool, bool(const QString&, const QString&));
    MOCK_METHOD3(setProperty, void(const QString&, const QString&, const QVariant&));
    MOCK_METHOD0(getScreenId, ScreenId());
};

Each method on the mocked interface needs to be enumerated inside a MOCK_METHOD macro, and the whole mock can be generated fairly easily. It feels like the team developing gtest got very close to not needing these headers at all,and with future versions of the C++ standard I wonder if they may be able to eliminate it altogether.

A simple test

So putting all this together, here’s what a basic unit test looks like:

TEST_F(TestLocalisationScreenController, onShow_setsCurrentlySelectedLocale)
{
    ON_CALL(*m_mockLocalisationModel, getSystemLocaleCode()).WillByDefault(Return("es_AR"));

    EXPECT_CALL(*m_mockScreen, setProperty(_, _, _)).Times(Exactly(2));
    EXPECT_CALL(*m_mockScreen, setProperty(QString("comboLocale"), QString("currentIndex"), QVariant(3))).Times(Exactly(1));

    m_localisationScreenController->onShow();
}

To dissect this, we can see the typical parts of a unit test, albeit with the gtest syntax:

  • We’re setting up some behaviour for the MockLocalisationModel, so that when the getSystemLocaleCode method is called, it will return "es_AR".

  • We set some expectations on the mock, i.e. we’re expecting a few calls to setProperty. More on this in a minute.

  • We call the method we’re testing, in this case onShow.

I like that the framework supports a sentence-style naming system, so the statements each give a roughly readable sentence of their intention:

On [a] call [to] m_mockScreen’s getSystemLocaleCode [method], [I] will by default return “es_AR”

and

Expect [a] call [to] m_mockScreen’s setProperty [method] [with any parameters] exactly 2 times

OK, it’s not quite 100%, but it’s good enough once you’re used to it.

You specify the expected parameters to be passed to mocked methods, so the key expectation in this example is:

EXPECT_CALL(*m_mockScreen, setProperty(QString("comboLocale"), QString("currentIndex"), QVariant(3))).Times(Exactly(1));

This tells the mock to expect a single call to setProperty with the strings "comboLocale", "currentIndex" and the variant 3. We’re expecting the controller to set the currentIndex property on the comboLocale UI component to a value of 3.

Note unfortunately, there are a few type-conversion issues using Qt core classes such as QString, so I’ve had to add constructors around each of these.

It is also important to notice the other expectation:

EXPECT_CALL(*m_mockScreen, setProperty(_, _, _)).Times(Exactly(2));

This tells the mock to expect two other calls to setProperty, with any (_) value for each of the parameters. This is because other properties are being set on the view during the call to onShow, which are not relevent to this test, but are happening nonetheless. We need to let the framework know about these other calls, otherwise the test rightly fails.

Inspecting the mock

In this example, we want to test that the correct data is being passed from the model to the view, specifically that a list component in the view contains the correct elements.

TEST_F(TestLocalisationScreenController, onShow_setsLocaleModel)
{
    QStringList sortedLocales;
    sortedLocales.append("English (GB)");
    sortedLocales.append("Spanish (Argentina)");
    sortedLocales.append("Spanish (Spain)");

    QVariant actualSetLocales;
    ON_CALL(*m_mockScreen, setProperty(QString(""), QString("localesModel"), _)).WillByDefault(SaveArg<2>(&actualSetLocales));

    EXPECT_CALL(*m_mockScreen, setProperty(_, _, _)).Times(Exactly(2));
    EXPECT_CALL(*m_mockScreen, setProperty(QString(""), QString("localesModel"), _)).Times(Exactly(1));

    m_localisationScreenController->onShow();

    ASSERT_EQ(actualSetLocales.toStringList(), sortedLocales);
}

Here we can demonstrate a couple of other pieces of syntax of the framework.

We can find out what has been passed to a mocked function (in this instance the method being tested is manipulating some data from the model and passing it to the view):

ON_CALL(*m_mockScreen, setProperty(QString(""), QString("localesModel"), _)).WillByDefault(SaveArg<2>(&actualSetLocales));

So we pass a local variable by reference to the SaveArg<2> method, which tells the mock to copy whatever gets passed to the specified method into the variable. This allows us then to use the an “assert equal” macro ASSERT_EQ to validate our expectation of what is passed here.

Summing up (for now)

Hopefully this is a useful if brief tour of what gtest looks like in practice, and although I’ve only scratched the surface of the tools available in the framework, hopefully these examples cover quite a large set of basic use cases if you’re getting started with or evaluating gtest.

Thanks for reading! Please share this post if you found it useful, check out my other posts, and of course, consider buying me a coffee!


comments powered by Disqus