Parametrized tests with parametrized SetUp()/TearDown()

Viewed 111

I have a test fixture that shares most of the test code. The variations are mostly from a parameter value. I would like to create SetUp() / TearDown() for them, but they (obviously) can't be called with a parameter. What would the best strategy to use SetUp() / TearDown() mechanisms while avoiding code duplication?

This is my original code :

class FileToolsTest : public testing::Test
{
   protected:

   void TestReadAndWrite(const wstring& filename, const wstring& content)
   {
      FileTools::RemoveFile(filename);

      // Test code there

      ok = FileTools::RemoveFile(filename);
      ASSERT_EQ(true, ok);
   }
};

//-------------------------------------------------------------------------

TEST_F(FileToolsTest, ReadAndWrite_case1)
{
   const wstring testFilename = L"testfile";
   const wstring testContent = L"This\nis\ndummy\ndata";
   TestReadAndWrite(testFilename, testContent);
}

TEST_F(FileToolsTest, ReadAndWrite_case2)
{
   const wstring testFilename = L"testfíle";
   const wstring testContent = L"This\nis\ndummy\n\nuníc@de datã éééé";
   TestReadAndWrite(testFilename, testContent);
}

TEST_F(FileToolsTest, Write_case3)
{
   const wstring testFilename = L"Micka\x00ebl/fileWithUnicodeFolderInPath2.txt";

   if (FileTools::FileExists(testFilename))
   {
      const bool ok = FileTools::RemoveFile(testFilename);
      ASSERT_TRUE(ok);
   }

   // Test code there

   FileTools::RemoveFile(testFilename);
}

As you can see, ::RemoveFile() is duplicated between TestReadAndWrite() and Write_case3(), both at test start and test end.

2 Answers

The example of Value-Parameterized Test.

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

struct FileTools {
  static bool RemoveFile(const std::wstring&);
};

struct FileToolsTestParam {
  const std::wstring testFilename;
  const std::wstring testContent;
} kFileToolsTestParam[] = {
  {
   L"testfile",
   L"This\nis\ndummy\ndata"
  },
  {
   L"testfíle",
   L"This\nis\ndummy\n\nuníc@de datã éééé"
  }
};

class FileToolsTest : public testing::TestWithParam<FileToolsTestParam> {
  protected:
   void SetUp() override {
     const auto& param = GetParam();
     FileTools::RemoveFile(param.testFilename);
   }
   void TearDown() override {
     const auto& param = GetParam();
     const bool ok = FileTools::RemoveFile(param.testContent);
     ASSERT_EQ(true, ok);
   }
};

INSTANTIATE_TEST_SUITE_P(FileTools, FileToolsTest, testing::ValuesIn(kFileToolsTestParam));

TEST_P(FileToolsTest, ReadAndWrite) {
  // Test code there
}

Note that you can make your parameters members of your test fixture class and still keep your tests' readability:

class FileToolsTest : public testing::Test {
 protected:

  void TearDown() override {
    const bool ok = FileTools::RemoveFile(testFilename_);
    ASSERT_EQ(true, ok);
  }

  void TestReadAndWrite() {
    FileTools::RemoveFile(testFilename_);
    // Test code there
  }

  // Test parameters:
  wstring testFilename_;
  wstring testContent_;
};

//-------------------------------------------------------------------------

TEST_F(FileToolsTest, ReadAndWrite_case1) {
  testFilename_ = L"testfile";
  testContent_ = L"This\nis\ndummy\ndata";
  TestReadAndWrite();
}

TEST_F(FileToolsTest, ReadAndWrite_case2) {
  testFilename_ = L"testfíle";
  testContent_ = L"This\nis\ndummy\n\nuníc@de datã éééé";
  TestReadAndWrite();
}

TEST_F(FileToolsTest, Write_case3) {
  testFilename_ = L"Micka\x00ebl/fileWithUnicodeFolderInPath2.txt";

  if (FileTools::FileExists(testFilename_)) {
    const bool ok = FileTools::RemoveFile(testFilename_);
    ASSERT_TRUE(ok);
  }

  // Test code there
}

Live example: https://godbolt.org/z/5q69rrEWc

You can even overload and make your TestReadAndWrite parameterized:


class FileToolsTest : public testing::Test {
 protected:

  void TearDown() override {
    const bool ok = FileTools::RemoveFile(testFilename_);
    ASSERT_EQ(true, ok);
  }

  void TestReadAndWrite() {
    FileTools::RemoveFile(testFilename_);
    // Test code there
  }

   void TestReadAndWrite(wstring testFilename, wstring testContent) {
    testFilename_ = testFilename;
    testContent_ = testContent;
    TestReadAndWrite();
  }

  // Test parameters:
  wstring testFilename_;
  wstring testContent_;
};

//-------------------------------------------------------------------------

TEST_F(FileToolsTest, ReadAndWrite_case1) {
  TestReadAndWrite(/*testFilename=*/ L"testfile", 
                   /*testContent=*/ L"This\nis\ndummy\ndata");
}

TEST_F(FileToolsTest, ReadAndWrite_case2) {
   TestReadAndWrite(/*testFilename=*/  L"testfíle", 
                    /*testContent=*/ L"This\nis\ndummy\n\nuníc@de datã éééé");
}

TEST_F(FileToolsTest, Write_case3) {
  testFilename_ = L"Micka\x00ebl/fileWithUnicodeFolderInPath2.txt";

  if (FileTools::FileExists(testFilename_)) {
    const bool ok = FileTools::RemoveFile(testFilename_);
    ASSERT_TRUE(ok);
  }

  // Test code there
}

Live example: https://godbolt.org/z/dzs9rYvqj

Related