• 玩转Google开源C++单元测试框架Google Test系列(gtest)之四 参数化


    一、前言

    在设计测试案例时,经常需要考虑给被测函数传入不同的值的情况。我们之前的做法通常是写一个通用方法,然后编写在测试案例调用它。即使使用了通用方法,这样的工作也是有很多重复性的,程序员都懒,都希望能够少写代码,多复用代码。Google的程序员也一样,他们考虑到了这个问题,并且提供了一个灵活的参数化测试的方案。

    二、旧的方案

    为了对比,我还是把旧的方案提一下。首先我先把被测函数IsPrime帖过来(在gtest的example1.cc中),这个函数是用来判断传入的数值是否为质数的。

    // Returns true iff n is a prime number.
    bool IsPrime(int n)
    {
        
    // Trivial case 1: small numbers
        if (n <= 1return false;

        
    // Trivial case 2: even numbers
        if (n % 2 == 0return n == 2;

        
    // Now, we have that n is odd and n >= 3.

        
    // Try to divide n by every odd number i, starting from 3
        for (int i = 3; ; i += 2) {
            
    // We only have to try i up to the squre root of n
            if (i > n/i) break;

            
    // Now, we have i <= n/i < n.
            
    // If n is divisible by i, n is not prime.
            if (n % i == 0return false;
        }
        
    // n has no integer factor in the range (1, n), and thus is prime.
        return true;
    }

    假如我要编写判断结果为True的测试案例,我需要传入一系列数值让函数IsPrime去判断是否为True(当然,即使传入再多值也无法确保函数正确,呵呵),因此我需要这样编写如下的测试案例:

    TEST(IsPrimeTest, HandleTrueReturn)
    {
        EXPECT_TRUE(IsPrime(
    3));
        EXPECT_TRUE(IsPrime(
    5));
        EXPECT_TRUE(IsPrime(
    11));
        EXPECT_TRUE(IsPrime(
    23));
        EXPECT_TRUE(IsPrime(
    17));
    }

    我们注意到,在这个测试案例中,我至少复制粘贴了4次,假如参数有50个,100个,怎么办?同时,上面的写法产生的是1个测试案例,里面有5个检查点,假如我要把5个检查变成5个单独的案例,将会更加累人。

    接下来,就来看看gtest是如何为我们解决这些问题的。

    三、使用参数化后的方案

    1. 告诉gtest你的参数类型是什么

    你必须添加一个类,继承testing::TestWithParam<T>,其中T就是你需要参数化的参数类型,比如上面的例子,我需要参数化一个int型的参数

    class IsPrimeParamTest : public::testing::TestWithParam<int>
    {

    };

    2. 告诉gtest你拿到参数的值后,具体做些什么样的测试

    这里,我们要使用一个新的宏(嗯,挺兴奋的):TEST_P,关于这个"P"的含义,Google给出的答案非常幽默,就是说你可以理解为”parameterized" 或者 "pattern"。我更倾向于parameterized"的解释,呵呵。在TEST_P宏里,使用GetParam()获取当前的参数的具体值。

    TEST_P(IsPrimeParamTest, HandleTrueReturn)
    {
        
    int n =  GetParam();
        EXPECT_TRUE(IsPrime(n));
    }

    嗯,非常的简洁!

    3. 告诉gtest你想要测试的参数范围是什么

     使用INSTANTIATE_TEST_CASE_P这宏来告诉gtest你要测试的参数范围:

    INSTANTIATE_TEST_CASE_P(TrueReturn, IsPrimeParamTest, testing::Values(35112317));

    第一个参数是测试案例的前缀,可以任意取。

    第二个参数是测试案例的名称,需要和之前定义的参数化的类的名称相同,如:IsPrimeParamTest

    第三个参数是可以理解为参数生成器,上面的例子使用test::Values表示使用括号内的参数。Google提供了一系列的参数生成的函数:

    Range(begin, end[, step]) 范围在begin~end之间,步长为step,不包括end
    Values(v1, v2, ..., vN) v1,v2到vN的值
    ValuesIn(container) and ValuesIn(begin, end) 从一个C类型的数组或是STL容器,或是迭代器中取值
    Bool() false 和 true 两个值
    Combine(g1, g2, ..., gN)

    这个比较强悍,它将g1,g2,...gN进行排列组合,g1,g2,...gN本身是一个参数生成器,每次分别从g1,g2,..gN中各取出一个值,组合成一个元组(Tuple)作为一个参数。

    说明:这个功能只在提供了<tr1/tuple>头的系统中有效。gtest会自动去判断是否支持tr/tuple,如果你的系统确实支持,而gtest判断错误的话,你可以重新定义宏GTEST_HAS_TR1_TUPLE=1

    四、参数化后的测试案例名

    因为使用了参数化的方式执行案例,我非常想知道运行案例时,每个案例名称是如何命名的。我执行了上面的代码,输出如下:


    从上面的框框中的案例名称大概能够看出案例的命名规则,对于需要了解每个案例的名称的我来说,这非常重要。 命名规则大概为:

    prefix/test_case_name.test.name/index

    五、类型参数化

    gtest还提供了应付各种不同类型的数据时的方案,以及参数化类型的方案。我个人感觉这个方案有些复杂。首先要了解一下类型化测试,就用gtest里的例子了。

    首先定义一个模版类,继承testing::Test:

    template <typename T>
    class FooTest : public testing::Test {
     
    public:
      
      typedef std::list
    <T> List;
      
    static T shared_;
      T value_;
    };

    接着我们定义需要测试到的具体数据类型,比如下面定义了需要测试char,int和unsigned int :

    typedef testing::Types<charint, unsigned int> MyTypes;
    TYPED_TEST_CASE(FooTest, MyTypes);

    又是一个新的宏,来完成我们的测试案例,在声明模版的数据类型时,使用TypeParam

    TYPED_TEST(FooTest, DoesBlah) {
      
    // Inside a test, refer to the special name TypeParam to get the type
      
    // parameter.  Since we are inside a derived class template, C++ requires
      
    // us to visit the members of FooTest via 'this'.
      TypeParam n = this->value_;

      
    // To visit static members of the fixture, add the 'TestFixture::'
      
    // prefix.
      n += TestFixture::shared_;

      
    // To refer to typedefs in the fixture, add the 'typename TestFixture::'
      
    // prefix.  The 'typename' is required to satisfy the compiler.
      typename TestFixture::List values;
      values.push_back(n);
      
    }

    上面的例子看上去也像是类型的参数化,但是还不够灵活,因为需要事先知道类型的列表。gtest还提供一种更加灵活的类型参数化的方式,允许你在完成测试的逻辑代码之后再去考虑需要参数化的类型列表,并且还可以重复的使用这个类型列表。下面也是官方的例子:

    template <typename T>
    class FooTest : public testing::Test {
      
    };

    TYPED_TEST_CASE_P(FooTest);

    接着又是一个新的宏TYPED_TEST_P类完成我们的测试案例:

    TYPED_TEST_P(FooTest, DoesBlah) {
      
    // Inside a test, refer to TypeParam to get the type parameter.
      TypeParam n = 0;
      
    }

    TYPED_TEST_P(FooTest, HasPropertyA) {  }

    接着,我们需要我们上面的案例,使用REGISTER_TYPED_TEST_CASE_P宏,第一个参数是testcase的名称,后面的参数是test的名称

    REGISTER_TYPED_TEST_CASE_P(FooTest, DoesBlah, HasPropertyA);

    接着指定需要的类型列表:

    typedef testing::Types<charint, unsigned int> MyTypes;
    INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes);

    这种方案相比之前的方案提供更加好的灵活度,当然,框架越灵活,复杂度也会随之增加。

    六、总结

    gtest为我们提供的参数化测试的功能给我们的测试带来了极大的方便,使得我们可以写更少更优美的代码,完成多种参数类型的测试案例。

    系列链接:

    1.玩转Google开源C++单元测试框架Google Test系列(gtest)之一 - 初识gtest

    2.玩转Google开源C++单元测试框架Google Test系列(gtest)之二 - 断言

    3.玩转Google开源C++单元测试框架Google Test系列(gtest)之三 - 事件机制

    4.玩转Google开源C++单元测试框架Google Test系列(gtest)之四 - 参数化

    5.玩转Google开源C++单元测试框架Google Test系列(gtest)之五 - 死亡测试

    6.玩转Google开源C++单元测试框架Google Test系列(gtest)之六 - 运行参数

    7.玩转Google开源C++单元测试框架Google Test系列(gtest)之七 - 深入解析gtest

    8.玩转Google开源C++单元测试框架Google Test系列(gtest)之八 - 打造自己的单元测试框架
  • 相关阅读:
    【BZOJ】1827: [Usaco2010 Mar]gather 奶牛大集会
    【BZOJ】1708: [Usaco2007 Oct]Money奶牛的硬币
    【BZOJ】1690: [Usaco2007 Dec]奶牛的旅行
    【BZOJ】1725: [Usaco2006 Nov]Corn Fields牧场的安排
    【BZOJ】1711: [Usaco2007 Open]Dining吃饭
    【BZOJ】1579: [Usaco2009 Feb]Revamping Trails 道路升级
    【BZOJ】1572: [Usaco2009 Open]工作安排Job
    【51NOD】1717 好数
    【POJ】3233 Matrix Power Series
    【BZOJ】1717: [Usaco2006 Dec]Milk Patterns 产奶的模式
  • 原文地址:https://www.cnblogs.com/coderzh/p/1431297.html
Copyright © 2020-2023  润新知