Dear Readers,

Today I’m going to share a small revelation that I made this week while using Google Test to test drive one of my project classes. As most people who used GTest know, it is possible to create tests that are using a predefined array of data to test some functionality, which are called parameterized tests. Just like the TEST_F macro, these tests also use fixtures, except that one first needs to define a test case class with proper member variables and constructor and then use this class to subclass a GTest provided, templated class to create the actual fixture. Finally, one needs to create the test with TEST_P using the fixture and then call INSTANTIATE_TEST_SUITE_P() to start the tests with a predefined set of data. The code below demonstrates such a scenario:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct ColorCase {
    uint32_t test, expected;
    ColorCase(uint32_t argTest, uint32_t argExpected)
        : test(argTest), expected(argExpected) {}
};

ColorCase rgbToBgrCases[] = {
    ColorCase(0xAABBCC, 0xCCBBAA),
    ColorCase(0x126754, 0x546712),
    ColorCase(0xA6B2C5, 0xC5B2A6)
};

struct ImageTest : public TestWithParam<ColorCase> {
    void SetUp() override {
        img = ImageClass(30, 30, COLOR_RGB);
        // ...
    }
    
    ImageClass img;
};

TEST_P(ImageTest, RrbToBgrConversion)
{
    // ...
}
INSTANTIATE_TEST_SUITE_P(BulkTest, ImageTest, ValuesIn(rgbToBgrCases));

Seems easy enough, but what if one wanted to use the same class to test a different functionality? Using the experience learned from using the TEST_F macro, one would simply write a new test with a new name using the same fixture, and start it with the instantiate macro, just as before:

C++

1
2
3
4
5
6
7
8
9
// ...

ColorCase rgbToYuvCases[] = { ... };

TEST_P(ImageTest, RrbToYuvConversion)
{
    // ...
}
INSTANTIATE_TEST_SUITE_P(BulkTest, ImageTest, ValuesIn(rgbToYuvCases));

The problem is, that contrary to TEST_F, it won’t work. The compiler error message will contain something like the following:

Make

1
2
3
4
5
error: redefinition of 'gtest_ImageTest_EvalGenerator_'
...
error: redefinition of 'gtest_ImageTest_EvalGenerateName_'
...
error: redefinition of 'gtest_ImageTest_dummy_'

To be honest, this was a bit surprising to me. I honestly thought this would work, but fortunately one does not need to despair, as inheritance can come to the rescue. By simply treating ImageTest as a base class never to be used and creating several empty subclasses out of it, one can easily overcome the issue mentioned above:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct ImageTest : public TestWithParam<ColorCase> {
    void SetUp() override {
        img = ImageClass(30, 30, COLOR_RGB);
        // ...
    }
    
    ImageClass img;
};

struct ImageTest1 : public ImageTest {};

TEST_P(ImageTest1, RrbToBgrConversion)
{
    // ...
}
INSTANTIATE_TEST_SUITE_P(BulkTest, ImageTest1, ValuesIn(rgbToBgrCases));


ColorCase rgbToYuvCases[] = { ... };

struct ImageTest2 : public ImageTest {};

TEST_P(ImageTest2, RrbToYuvConversion)
{
    // ...
}
INSTANTIATE_TEST_SUITE_P(BulkTest, ImageTest2, ValuesIn(rgbToYuvCases));

This method does not only allow the reuse of the original class, but changing the arguments for the ImageClass constructor is now also just a new SetUp() override away, not to mention the possibility of other class wide modifications/additions. Obviously, the benefits of such a scenario is more visible when the fixture contains more data members or a more comprehensive SetUp(), but for now let’s deduce today’s lesson:

Use inheritance to overcome one of the limitations of parameterized tests in Google Test

As always, thanks for reading.