使用谷歌测试进行数据驱动的单元测试

sig*_*igy 8 c++ testing unit-testing googletest data-driven-tests

我目前正在使用googles单元测试框架为嵌入式应用程序编写单元测试.现在我的老板很不高兴我测试的数据(即我调用被测试类的方法的值)在测试中是硬接线的.他要求从文件中读取此数据.他的论点是,为一个以前被遗忘的角落案件添加另一个测试会更容易.我不是单位测试的经验,但到目前为止,我不是这样做的.所以我试着弄清楚最好的方法是什么 - 即使这样做是个好主意.我很快就遇到了DDT(数据驱动测试)方法.

谷歌单元测试框架有一个他们称之为" 价值参数化测试 "的功能.有了它,我的测试夹具成为模板类,我可以传入参数.但是,我看到了一些问题:

  • 我目前每个班级都有一个夹具正在测试中.但是我需要每个测试方法都有一个夹具,因为每个方法都需要一组不同的参数.这将是一项额外的工作.
  • 在我看来,我只能传入一个参数.因为我的测试需要几个(我的方法的所有参数加上预期的结果)这需要我传递类似矢量或地图的东西.同样,这种构造和检索听起来像很多工作.

我会想象像谷歌测试框架那样成熟的东西让它变得更容易.但是,他们写道

值参数化测试很方便[当]你想通过各种输入(又称数据驱动测试)测试你的代码.此功能很容易被滥用,所以请在锻炼时锻炼你的良好感觉!

此外还有这篇博客文章TotT:Data Driven Traps,也警告我(滥用)数据驱动的单元测试.

所以我的问题归结为:

  • 进行数据驱动的单元测试是个好主意吗?
  • 如何使用谷歌测试框架进行数据驱动的单元测试

我并不是真的要googletest,而且基本上可以自由选择任何我喜欢的框架.

编辑

我在googletest常见问题解答的FAQ条目中找到了以下声明

一般来说,Google Test对数据驱动的测试还没有很好的支持.我们希望很快能够在这方面做出改进.

Pio*_*ycz 7

GTest支持它 - 但也许他们不知道......

使用testing::ValuesIn- 就像在这个简化的例子中:

class SomeTests : public TestWithParam<int>
{
public:

};

TEST_P(SomeTests, shouldBePositive)
{
    ASSERT_GT(GetParam(), 0);
}
Run Code Online (Sandbox Code Playgroud)

而且 - 如何从输入流中获取值的方式:

std::ifstream inputValuesFromFile("input.txt");
INSTANTIATE_TEST_CASE_P(FromFileStream,
                        SomeTests,
                        ValuesIn(std::istream_iterator<int>(inputValuesFromFile), 
                                 std::istream_iterator<int>()));
Run Code Online (Sandbox Code Playgroud)

要使用比"int"更复杂的类型,你需要为它编写一个运算符>>,如:

struct A
{
    int a;
    std::vector<int> b;
}
std::istream& operator >> (std::istream& is, A& out)
{
     std::size_t bSize;
     if ((is >> A.a) && (is >> bSize))
     {
         out.b.reserve(bSize);
         while (bSize-- > 0)
         {
             int b;
             if (!(is >> b))
                break;
             out.b.push_back(b);   
         }
     }
     return is;
}
Run Code Online (Sandbox Code Playgroud)

当然 - 在更复杂的情况下 - 考虑使用类似XMl(json?)的格式和一些比它更专业的迭代器std::istream_iterator<T>.


对于类似XML的格式 - 你可能会考虑这样的方案(这是非常假设的代码 - 我脑子里没有任何这样的库):

SomeLib::File xmlData("input.xml");

class S1BasedTests : public TestWithParam<S1>
{};

TEST_P(S1BasedTests , shouldXxxx)
{
    const S1& s1 = GetParam();
    ...
}
auto s1Entities = file.filterBy<S1>("S1");
INSTANTIATE_TEST_CASE_P(S1,
                        S1BasedTests,
                        ValuesIn(s1Entities.begin(), s1Entities .end()));
Run Code Online (Sandbox Code Playgroud)

//等等你想要的任何类型的S1


如果市场上没有这样的C++类型友好库(我搜索了2分钟但没找到) - 那么可能是这样的:

SomeLib::File xmlFile("input.xml");

struct S1BasedTests : public TestWithParam<SomeLib::Node*>
{
   struct S1 // xml=<S1 a="1" b="2"/>
   {
       int a;
       int b;
   };
   S1 readNode()
   {
        S1 s1{};
        s1.a = GetParam()->getNode("a").getValue<int>();
        s1.b = GetParam()->getNode("b").getValue<float>();
        return s1;
   }
};

TEST_P(S1BasedTests , shouldXxxx)
{
    const S1& s1 = readNode();
    ...
}
INSTANTIATE_TEST_CASE_P(S1,
                        S1BasedTests ,
                        ValuesIn(xmlFile.getNode("S1").getChildren()));
                        // xml=<S1s> <S1.../> <S1.../> </S1>
Run Code Online (Sandbox Code Playgroud)

//等等任何节点类型,如S1