Brain Dump

GTest

Tags
library

Is the standard testing framework for CPP provided by Google.

Tests

#include <gmock/gmock.h>
#include <gtest/gtest.h>

using namespace ::testing;

template <class T>
T square(T i) { return i * i; }

// You declare a test case with the TEST macro.
//
// The first argument is the namespace/scope of the test case
// and the second argument is the test name.
TEST(Square, testThatRegularInputGivesCorrectAnswer) {
    // You can stream values into assertions to provide more debug output.
    EXPECT_THAT(square(4.0), Eq(16.0)) << "Did not square correctly";
}

Fixtures

Fixtures in GTest are unlike ones you may have grown accustomed to in Python. In GTest a fixture is a class which is initialised with the test case, or a suite of test cases related to that fixture, and whose protected members can be accessed from within the test case. All fixtures in GTest must inherit from testing::test.

class MyFixture {
  protected:
    void SetUp() override {
	testNum = 5;
	testValue = 25;
    }

    double testNum;
    double testValue;
};

// Tests using a fixture use TEST_F and take the fixture class as,
// the namespace argument.
TEST_F(MyFixture, squareWorksWithMyFixtureValues) {
    // We can implicitly access protected members in the test case.
    EXPECT_THAT(square(testNum), EQ(testValue));
}

You should use fixtures to initialise some state (such as a queue or database) that your test cases will reuse. You can also use it as a way to test non-public methods of a class by making those methods public and having the fixture inherit from the class as well.

Parameterised Tests

Work similar to fixtures for GTest. We define a test deriving from testing::TestWithParam<T> or including the testing::WithParamInterface<T> mixin.

std::string frobulate(const string &foo) { return foo + " frob"; }

typedef std::pair<std::string, std::string> FooParam;
class FooFixture : TestWithParam<FooParam> {};

// Declare a parametrised test case using FooFixture.
TEST_P(FooFixture, frobulates) {
    std::string foo = GetParam().first;
    std::string expected = GetParam().second;
    EXPECT_THAT(frobulate(foo), EQ(expected));
}

// Instantiate all test cases using FooFixture in the Foo namespace
// with 2 parameter sets.
INSTANTIATE_TEST_CASE_P(Foo, FooFixture,
			Values(FooParam("foo", "foo frob"),
			       FooParam("bar", "bar frob")));

Mocking

GTest exposes mocking through the GMock library.

class Foo {
  public:
    virtual void foo(int fob);
};

class MockFoo : public Foo {
  public:
    // Note: The following must be in the public section, even though
    // the methods may be protected or private in the base class.
    MOCK_METHOD1(foo, void(int));

    // gmock provides numerous similair macros for mocking functions
    // with different types. MOCK_METHOD0 takes no arguments, MOCK_METHOD1
    // takes 1 argument etc. If the method being mocked is suffixed by const
    // meaning it doesn't modify the object you can use MOCK_CONST_METHOD1.
};

TEST(Foo, fooTest) {
    MockFoo my_foo;
    // Declare that method foo on object my_foo will be called 3 at least twice
    // in this test. Make it return 0 the first time, 1 the second time and for
    // any calls beyond that return 2.
    EXPECT_CALL(my_foo, foo(_))
	.Times(AtLeast(2))
	.WillOnce(Return(0))
	.WillOnce(Return(1))
	.WillRepeatedly(Return(2));

    // Some logic that causes foo to be called.
    // ...
}

Warn: To mock a method with GMock you must declare that method as virtual in the original class. This is to ensure method resolution when using references of the parent class use the implementation in the mocked class.

Wildcards and Deactivating a Mock

When mocking a method you can filter the responses based on the arguments.

TEST(Foo, fooTest) {
    MockFoo my_foo;

    EXPECT_CALL(my_foo, foo("google.com"))
	.WillRepeatedly(Return(2));
    EXPECT_CALL(my_foo, foo("apple.com"))
	.WillRepeatedly(Return(3));

    my_foo.foo("google.com"); //=> 2
    my_foo.foo("apple.com");  //=> 3
    my_foo.foo("apple.com");  //=> 3
    my_foo.foo("google.com"); //=> 2
}

The way this works is that every time we do an EXPECT_CALL GMock will push the configuration into a stack. Then when a later call to the mocked method comes in it looks through all the entries on the stack (LIFO obviously) until it matches the parameter list and then returns the correct value.

To simplify this the wildcard parameter _ can be given and will always match any argument passed for that parameter. Note: This could have unintended affect where one wildcard is so generic it always matches the arguments, when you only wanted it to match until it is saturated (has been used up the expected number of times). To fix this you can use RetiresOnSaturation.

TEST(Foo, fooTest) {
    MockFoo my_foo;

    EXPECT_CALL(my_foo, foo("google.com"))
	.WillRepeatedly(Return(2));
    EXPECT_CALL(my_foo, foo(_))
	.Times(2)
	.WillRepeatedly(Return(3))
	.RetiresOnSaturation();

    my_foo.foo("google.com"); //=> 2
    my_foo.foo("google.com"); //=> 2
    my_foo.foo("google.com"); //=> 3
}

Default Return Values

Matchers

Are how GTest performs an assertion. They compare one value with another and produce semantically relevant output if the comparison fails. GTest comes with numerous built in matchers and some macros to simplify them.

You can find a comprehensive list of available matchers here.

Note: In some cases GTest provides a helper macro to simplify common matchings. For example EXPECT_EQ(foo, bar) == EXPECT_THAT(foo, EQ(bar)).

EXPECT vs ASSERT

GTest actually provides 2 assertion macros. EXPECT performs an assertion and fails the test if the assertion fails, but doesn't terminate the execution of the test. The test case can continue to perform other expectations until it's terminated. ASSERT immediately terminates a test case when an assertion fails. This is to avoid triggering undefined behaviour.

Use ASSERT whenever the remaining tests in the test case requires the current assertion to be true, otherwise use EXPECT. For example if the rest of the tests in a test case require a file to be successfully opened, it makes little sense to perform those assertions if the file failed to be opened.

Links to this note