• Google C++单元测试框架(Gtest)系列教程之四——参数化


    引言

    在上一篇文章中,我们学习了如何使用Gtest的测试固件(Test fixture)完成测试代码和测试数据的复用,这一节我们来学习如何使用Gtest值参数化的方法,简化函数测试;使用类型参数化的方法,简化对模板类的测试。

    值参数化

    假设我们要对以下函数进行测试:

    // 判断n是否为质数
    bool IsPrime(int n)

    假设我们要编写判定结果为false的测试案例,根据之前学习的断言和TEST()的使用方法,我们编写测试代码如下:

    // Tests negative input.
    TEST(IsPrimeTest, Negative) {
    EXPECT_FALSE(IsPrime(-1));
    EXPECT_FALSE(IsPrime(-2));
    EXPECT_FALSE(IsPrime(-5));
    EXPECT_FALSE(IsPrime(-100));
    EXPECT_FALSE(IsPrime(INT_MIN));
    }

    显然我们对“EXPECT_FALSE(IsPrime(X))”这样的语句复制粘贴了5次,但当被测数据有几十个上百个的时候,再使用复制粘帖的方式就弱爆了。下面我们来看Gtest中为解决这个问题,给我们提供的方法。
    首先,我们添加一个继承自::testing::TestWithParam<T>的类,其中T就是我们被测数据的类型,针对以上函数IsPrimeTest,添加以下类:

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

    在该类中,我们可以编写SetUp()和TearDown()函数,分别完成数据初始化和数据清理,还可以添加类成员、其他类成员函数,相关的用法,可以参看Gtest Project的这个例子,这里我们仅对函数作测试,SetUp()等方法都不需要用到,IsPrimeParamTest为一个空的类。

    接着我们需要使用宏TEST_P来编写相应的测试代码:

    TEST_P(IsPrimeParamTest, Negative)
    {
    int n = GetParam();
    EXPECT_FALSE(IsPrime(n));
    }

    GetParam()方法用于获取当前参数的具体值,这段测试代码相比上面的是不是精简多了?!

    最后,我们使用INSTANTIATE_TEST_CASE_P()告知Gtest我们的被测参数都有哪些:

    INSTANTIATE_TEST_CASE_P(NegativeTest, IsPrimeParamTest, testing::Values(-1,-2,-5,-100,INT_MIN));

    以上第一个参数为测试实例的前缀,可以随意取;第二个参数为测试类的名称;第三个参数指示被测参数,test::Values表示使用括号内的参数。运行该测试用例,得到结果如下:

    Running main() from gtest_main.cc
    [==========] Running 5 tests from 1 test case.
    [----------] Global test environment set-up.
    [----------] 5 tests from NegativeTest/IsPrimeParamTest
    [ RUN ] NegativeTest/IsPrimeParamTest.Negative/0
    [ OK ] NegativeTest/IsPrimeParamTest.Negative/0 (0 ms)
    [ RUN ] NegativeTest/IsPrimeParamTest.Negative/1
    [ OK ] NegativeTest/IsPrimeParamTest.Negative/1 (0 ms)
    [ RUN ] NegativeTest/IsPrimeParamTest.Negative/2
    [ OK ] NegativeTest/IsPrimeParamTest.Negative/2 (0 ms)
    [ RUN ] NegativeTest/IsPrimeParamTest.Negative/3
    [ OK ] NegativeTest/IsPrimeParamTest.Negative/3 (0 ms)
    [ RUN ] NegativeTest/IsPrimeParamTest.Negative/4
    [ OK ] NegativeTest/IsPrimeParamTest.Negative/4 (0 ms)
    [----------] 5 tests from NegativeTest/IsPrimeParamTest (1 ms total)

    [----------] Global test environment tear-down
    [==========] 5 tests from 1 test case ran. (1 ms total)
    [ PASSED ] 5 tests.

    从结果上可以看出每个测试实例的全称为:前缀/测试用例名称.测试实例名称。

    类型参数化

    像能以参数的形式列出被测值一样,我们也可以以参数的形式列出被测类型,这极大地方便了对模板类的测试。针对编写测试代码前已知被测类型和未知被测类型两种情况,Gtest还为我们提供了两种不同的方法,下面假设我们分别使用两种方法对以下类进行测试:

    template <typename E> // E is the element type.
    class Queue {
    public:
    Queue();
    void Enqueue(const E& element);
    E* Dequeue(); // Returns NULL if the queue is empty.
    size_t size() const;
    ...
    };


    方法一:已知被测类型

    对于以上模板类Queue,假设我们在编写测试代码之前已知需要对其作int和char类型的测试,首先我们需要编写生成具体类型的Queue工厂方法:

    template <class T>
    Queue<T>* CreateQueue();
    template <>
    Queue<int>* CreateQueue<int>()
    {
    return new Queue<int>;
    }
    template <>
    Queue<char>* CreateQueue<char>()
    {
    return new Queue<char>;
    }

    然后我们需要编写测试固件类模板(test fixture class template):

    template <class T>
    class QueueTest:public testing::Test
    {
    protected:
    QueueTest():queue(CreateQueue<T>()){}
    virtual ~QueueTest(){delete queue;}
    Queue<T>* const queue;
    };

    可以看到QueueTest的构造函数中使用了工厂方法对类成员变量queue进行初始化。然后我们需要声明和注册我们要测试的类型:

    using testing::Types;
    // The list of types we want to test.
    typedef Types<int, char> Implementations;

    TYPED_TEST_CASE(QueueTest, Implementations);

    这里又用到了一个新的宏:TYPED_TEST_CASE(TestCaseName, TypeList),第一个参数为测试用例名称,第二个参数为类型列表。最后我们使用TYPED_TEST宏编写检测代码:

    // 检测对象生成后,queue的大小是否为0
    TYPED_TEST(QueueTest, DefaultConstructor) {
    EXPECT_EQ(0u, this->queue->Size());
    }

    编译、运行该测试程序,得到测试结果如下:

    Running main() from gtest_main.cc
    [==========] Running 2 tests from 2 test cases.
    [----------] Global test environment set-up.
    [----------] 1 test from QueueTest/0, where TypeParam = int
    [ RUN ] QueueTest/0.DefaultConstructor
    [ OK ] QueueTest/0.DefaultConstructor (0 ms)
    [----------] 1 test from QueueTest/0 (1 ms total)

    [----------] 1 test from QueueTest/1, where TypeParam = char
    [ RUN ] QueueTest/1.DefaultConstructor
    [ OK ] QueueTest/1.DefaultConstructor (0 ms)
    [----------] 1 test from QueueTest/1 (0 ms total)

    [----------] Global test environment tear-down
    [==========] 2 tests from 2 test cases ran. (1 ms total)
    [ PASSED ] 2 tests.


    方法二:未知类型

    在编写测试案例的时候,我们可能并不知道该Queue类会被哪些类型实例化、被测类型是什么,如此是否就无法编写测试案例?!安心する,Gtest为我们提供了方法,可让我们先写检测案例,具体要测的类型可以后期补上,这种方法如下:

    与方法一相同,我们需要使用生成Queue的工厂方法,并定义固件类模板,这里图方便,继承了以上QueueTest模板类。

    template <class T>
    class QueueTest2:public QueueTest<T>{
    };

    接下来,使用宏TYPED_TEST_CASE_P声明测试用例,其参数为固件模板类的名称。

    TYPED_TEST_CASE_P(QueueTest2);

    然后我们就可以使用宏TYPED_TEST_P编写测试案例了:

    // 检测对象生成后,queue的大小是否为0
    TYPED_TEST_P(QueueTest2, DefaultConstructor) {
    EXPECT_EQ(0u, this->queue->Size());
    }

    相比方法一,该方法还多了一步:注册测试实例。这里需要使用到REGISTER_TYPED_TEST_CASE_P宏:

    REGISTER_TYPED_TEST_CASE_P(QueueTest2, DefaultConstructor);

    通过以上基本,我们大部分的未知类型测试代码都编写完成了,但我们并没有一个真正意义上的测试实例,因为我们还没有指定测试类型。通常我们将以上测试代码写进一个.h头文件中,任何想要使用具体类型实例化的代码都可以#include该头文件。
    假设我们要测试int和char类型,我们可以在一个.cc文件中编写以下代码:

    typedef Types<int, char> Implementations;

    INSTANTIATE_TYPED_TEST_CASE_P(QueueInt_Char, QueueTest2, Implementations);

    同样,我们需要使用Types列出被测类型,注意到这里列出被测类型是在使用TYPED_TEST_P编写测试实例之后。


    小结

    这一节我们学习了如何使用Gtest值参数化的方法简化函数测试,如何使用已知类型参数化、未知类型参数化的方法简化对模板类的测试。Gtest project中给了我们一个值和类型均为自定义类的例子,感兴趣的话可以猛击这里

    Reference:googletest project

                 《玩转Google开源C++单元测试框架Google Test系列(gtest)》by CoderZh

  • 相关阅读:
    自主学习之RxSwift(一) -----Driver
    RxSwift:ReactiveX for Swift 翻译
    C简单实现动态顺序表
    C简单实现双向链表
    C实现单链表
    享受这一点点的平淡
    C文件读写
    大神都在看的RxSwift 的完全入坑手册
    字符串常量存在何处
    认识自己
  • 原文地址:https://www.cnblogs.com/bangerlee/p/2199701.html
Copyright © 2020-2023  润新知