Specify constructor arguments for a Google test Fixture
Asked Answered
C

6

26

With Google test I want to specify a Test fixture for use in different test cases. The fixture shall allocate and deallocate objects of the class TheClass and its data management class TheClassData, where the data management class requires the name of a datafile.
For the different tests, the file name should vary.

I defined the following Fixture:

class TheClassTest : public ::testing::Test {
 protected:
  TheClassTest(std::string filename) : datafile(filename) {}
  virtual ~TheClassTest() {}
  virtual void SetUp() {
    data = new TheClassData(datafile);
    tc = new TheClass(data);
  }
  virtual void TearDown() {
    delete tc;
    delete data;
  }

  std::string datafile;
  TheClassData* data;
  TheClass* tc;
};

Now, different tests should use the fixture with different file names. Imagine this as setting up a test environment.

The question: How can I specify the filename from a test, i.e. how to call a non-default constructor of a fixture?

I found things like ::testing::TestWithParam<T> and TEST_P, which doesn't help, as I don't want to run one test with different values, but different tests with one fixture.

Clementeclementi answered 5/7, 2016 at 15:31 Comment(3)
So you want to run that fixture yourself? The google test default test runner cannot instantiate fixtures with parameters.Aedes
I want to run a test (probably TEST_F) using the fixture. So the answer is, that it's not possible? Thanks.Clementeclementi
I think TestWithParam<T> and TEST_P is exactly what you need. Lookup the Advanced Docs how to use them in practice. You can always instantiate the instance under test (I assume it's TheClass) inside of the testcase.Aedes
N
26

As suggested by another user, you cannot achieve what you want by instantiating a fixture using a non-default constructor. However, there are other ways. Simply overload the SetUp function and call that version explicitly in the tests:

class TheClassTest : public ::testing::Test {
protected:
    TheClassTest() {}
    virtual ~TheClassTest() {}
    void SetUp(const std::string &filename) {
        data = new TheClassData(filename);
        tc = new TheClass(data);
    }
    virtual void TearDown() {
        delete tc;
        delete data;
    }

    TheClassData* data;
    TheClass* tc;
};

Now in the test simply use this overload to set up filename:

TEST_F(TheClassTest, MyTestCaseName)
{
    SetUp("my_filename_for_this_test_case");

    ...
}

The parameterless TearDown will automatically clean up when the test is complete.

Nympho answered 6/7, 2016 at 7:41 Comment(3)
Great. Thanks a lot!Clementeclementi
The SetUp method will still be called by gtest, resulting in two calls to SetUp ... so in this case there is a memory leak since TearDown only deletes the last allocations. I would rename SetUp to something else so that gtest does not call it.Howze
Sorry, I am wrong. Since your Setup takes a parameter it won't be called by gtest. I would rename SetUp to something else since it can be confused with SetUp() which is called by gtest.Howze
K
10

Use the current class as a base class for your fixtures:

class TheClassTestBase : public ::testing::Test {
 protected:
  TheClassTestBase(std::string filename) : datafile(filename) {}
  ...
 };

For every specific filename - use derived fixture:

class TheClassTestForFooTxt : public TheClassTestBase {
protected:
    TheClassTestForFooTxt() : TheClassTestBase ("foo.txt") {}
};

However this is extra step needed for every set of parameters - so you can try to use templates or macros to get it done with less effort. Like:

template <typename ClassTestTag>
struct ClassTestParams
{
    static std::string filename;
};

template<typename  ClassTestTag>
class TheClassTest : public TheClassTestBase {
protected:
    TheClassTest() : TheClassTestBase (ClassTestParams<ClassTestTag>::filename) {}
};

Then - for every set of parameters - do that:

class FooTxtTag {};
template <> std::string ClassTestParams<FooTxtTag>::value = "foo.txt";
using TheClassTestForFooTxt = TheClassTest<FooTxtTag>;
TEST_F(TheClassTestForFooTxt, xxxx) {}

However - in your specific case - I would also try GoogleTest:type-parameterized-tests.

Kalynkam answered 6/7, 2016 at 8:19 Comment(2)
Thanks. I think this is a bit too much effort for the size of my problem.Clementeclementi
link to doc is brokenRangy
I
5

Another great way to deal with this is to just extend your fixture and in the extended class supply a new default constructor which calls through to the old one with the arguments you require. For example:

struct MySpecializedTestFixture : public GenericTestFixture
{
  MySpecializedTestFixture() : GenericTestFixture("a thing", "another thing") {}
};

TEST_F(MySpecializedTestFixture, FancyTest)
{
  // Use the thing environment and make some assertions.
}
Ideography answered 1/11, 2018 at 13:34 Comment(0)
F
0

If you overload the SetUp method as suggested here, and you want to ensure that you remember to use the overloaded SetUp, you can use an assertion in the TearDown method.

class my_fixture : public ::testing::Test
{
protected:
    bool SETUP_HIT_FLAG = false;

    void SetUp(double parameter)
    {
        ...
        SETUP_HIT_FLAG = true;
    }

    void TearDown() override
    {
        assert(SETUP_HIT_FLAG && "You forgot to call SetUp with your parameter!");
    }
};
Flat answered 28/5, 2019 at 19:22 Comment(0)
F
0

Another way using templates:

template<int N>
class Fixture : public ::testing::Test { ... }

using FixtureForTest = Fixture<1000>;
TEST_F(FixtureForTest, test) { ... }
Fantastically answered 17/11, 2022 at 8:55 Comment(0)
V
-3

For this specific case, I feel it is much easier to get rid of the test fixture altogether. The SetUp function can instead be replaced with a helper function that instantiates the class with the required file name. This permits the use of TEST instead of TEST_P or TEST_F. Now each test case is a standalone test which creates its own test class instances with the helper function or directly in the body of the test case.

For example:

using namespace testing;
TEST(FooClassTest, testCase1)
{
    FooClass fooInstance("File_name_for_testCase1.txt");
    /* The test case body*/
    delete fooInstance;
}
Valarievalda answered 19/4, 2018 at 9:8 Comment(1)
The OP asked about using a test fixture. This example doesn't show how to reuse datafile, data and tc. And what is up with delete fooInstance ?Grainy

© 2022 - 2024 — McMap. All rights reserved.