Nikita
Nikita

Reputation: 639

How to run Parameterized Tests with fixture member values in Google Test (gtest)?

What I want to achieve is a Paramaterized Test TEST_P(MyFixtureClass, DoStuff), with which I can test different values. Though said values shouldn't be constants, like those typically passed to INSTANTIATE_TEST_CASE_P. Moreover, I would want to use the values within some other fixture class - ideally.

There doesn't seem to be anything out there, which covers using fields instead of static values when creating parameterized tests. The official documentation doesn't seem to cover this either - sadly.


But to avoid introducing the XY-problem in this question, here is the equivalent pseudo code:

The parameterized fixture, MyFixture:

struct MyFixture : OtherFixture, ::testing::WithParamInterface<float>
{
    float a;

    void SetUp() override
    {
        a = GetParam();
    }
};

OtherFixture would look like this:

struct OtherFixture : testing::Test
{
    float a;
    float b;
    float c;

    void SetUp() override
    {
        a = CalculateSomeFloat();
        b = CalculateSomeFloat();
        c = CalculateSomeFloat();
    }
};

The test case would be something like:

// This here is the key aspect.
// Basically, I do not want to write a bunch of tests for a, b and c.
// Rather, I'd just test all 3 with this one.
TEST_P(MyFixture, DoStuff)
{
    ...bunch of tests
}

And lastly, we would instantiate the parameterized tests:

INSTANTIATE_TEST_CASE_P(MyFloatTesting, MyFixture, ::testing::Values(
    OtherFixture::a, OtherFixture::b, OtherFixture::c
));

Obviously, OtherFixture::a is inappropriate, but it illustrates where I would want to refer to a field, within a inherited fixture class (or any fixture class for that matter).


So is there any way to achieve this with gtest? I do not necessarily need to use parameterized tests. Simply avoiding having to write the same tests, for different objects is fine by me.


Any suggestions are much appreciated!

Upvotes: 15

Views: 17906

Answers (1)

PiotrNycz
PiotrNycz

Reputation: 24430

I think you need to use ::testing::Combine.

And change the parameters from float to std::tuple<float, float OtherFixture::*>.

using OtherFixtureMemberAndValue = std::tuple<float, float OtherFixture::*>;

struct MyFixture : OtherFixture, ::testing::WithParamInterface<OtherFixtureMemberAndValue>
{
    float a = std::get<0>(GetParam());
    auto& memberToTest()
    {
        return this->*std::get<1>(GetParam());
    }


};

To define set of parameters use this approach:

const auto membersToTest = testing::Values(
     &OtherFixture::a, 
     &OtherFixture::b, 
     &OtherFixture::c
);

const auto floatValuesToTest = testing::Values(
    2.1, 
    3.2
    //  ... 
 );

INSTANTIATE_TEST_CASE_P(AllMembers,
                        MyFixture,
                        testing::Combine(floatValuesToTest, membersToTest));

Then you can write your tests generic with respect to members of OtherFixture:

TEST_P(MyFixture, test)
{
    ASSERT_EQ(a, memberToTest());
}

I would also advice that you wrote PrintTo for float OtherFixture::*:

void PrintTo(float OtherFixture::*member, std::ostream* os)
{
    if (member == &OtherFixture::a)
        *os << "&OtherFixture::a";
    else if (member == &OtherFixture::b)
        *os << "&OtherFixture::b";
    else if (member == &OtherFixture::c)
        *os << "&OtherFixture::c";
    else
        *os << "&OtherFixture::? = " << member;

}

In this way you get nice message in case of failure:

[ FAILED ] AllMembers/MyFixture.test/5, where GetParam() = (3.2, &OtherFixture::c)

comparing to nasty, meaningless message w/o PrintTo:

[ FAILED ] AllMembers/MyFixture.test/5, where GetParam() = (3.2, 4-byte object <10-00 00-00>)

Upvotes: 17

Related Questions