• Google C++单元测试框架(Gtest)系列教程之三——测试固件(Test fixture)


    引言

    《Google C++单元测试框架(Gtest)系列教程之二——断言、函数测试》中,我们了解了断言语句,以及如何运用TEST()进行函数测试,在TEST()的使用中,我们接触了一个测试用例包含多个测试实例的组织方式。多个测试实例可能需要进行相识的数据配置和初始化操作,为此,Gtest提供了测试固件(Test fixture)帮助我们进行数据管理。

    “落后”的方法

    在了解测试固件之前,我们先来看以下测试例子:

    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类进行测试,根据我们之前学习到的TEST()的用法,编写测试代码如下:

    //测试方案一
    TEST(QueueTest, IsEmptyInitially) {
      Queue<int> q0_;
    EXPECT_EQ(0, q0_.size());
    }
    TEST(QueueTest, DequeueWorks) {
      Queue<int> q0_;
     
    Queue<int> q1_;
     
    Queue<int> q2_;

      q1_.Enqueue(1);
      q2_
    .Enqueue(2);
      q2_
    .Enqueue(3);

    int* n = q0_.Dequeue();
    EXPECT_EQ(NULL, n);

    n = q1_.Dequeue();
    ASSERT_TRUE(n != NULL);
    EXPECT_EQ(1, *n);
    EXPECT_EQ(0, q1_.size());
    delete n;

    n = q2_.Dequeue();
    ASSERT_TRUE(n != NULL);
    EXPECT_EQ(2, *n);
    EXPECT_EQ(1, q2_.size());
    delete n;
    }

    不知你是否已经发现问题所在呢?对,红色字体的测试数据初始化部分存在重复代码!在该例子中仅包含两个测试实例,重复代码的问题并不突出,但对于几十个甚至上百个测试实例而言,我们就需要另一种方式管理我们的初始化数据了。

    测试固件(Test fixture)

    测试固件的作用在于管理两个或多个测试实例都会使用到的数据,使用测试固件完成上述测试,方法如下:

    首先我们需要定义一个固件类(fixture class),一般固件类以FooTest的形式命名,其中Foo为被测类的名称:

    class QueueTest : public ::testing::Test {
    protected:
    virtual void SetUp() {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
    }
    // virtual void TearDown() {}
    Queue<int> q0_;
    Queue<int> q1_;
    Queue<int> q2_;
    };

    定义固件类的方法为:

    1. 写一个继承自::test::Test的类,为使该类的子类能访问到该类的数据,使用public或protected作为访问控制标识;
    2. 在该类中,定义测试实例将用到的数据;
    3. 使用SetUp()方法或默认构造函数作数据初始化操作,使用TearDown()方法或析构函数作数据清理操作,注意SetUp()和TearDown()的拼写;
    4. 如有需要,还可以在该类中定义成员函数,正如初始化数据,这里所定义的成员函数也可被测试实例重复使用。

    接下来我们来看如何编写相应的测试实例,首先我们要用到一个新的宏:

    TEST_F(test_case_name, test_name) {
    ... test body ...
    }

    TEST_F()必须在测试固件定义之后才能使用,其两个参数含义与TEST()的参数含义相同,但TEST_F()的第一个参数必须为固件类的名称。


    结合上述QueueTest测试固件,我们编写测试代码如下:

    //测试方案二
    TEST_F(QueueTest, IsEmptyInitially) {
    EXPECT_EQ(0, q0_.size());
    }

    TEST_F(QueueTest, DequeueWorks) {
    int* n = q0_.Dequeue();
    EXPECT_EQ(NULL, n);

    n = q1_.Dequeue();
    ASSERT_TRUE(n != NULL);
    EXPECT_EQ(1, *n);
    EXPECT_EQ(0, q1_.size());
    delete n;

    n = q2_.Dequeue();
    ASSERT_TRUE(n != NULL);
    EXPECT_EQ(2, *n);
    EXPECT_EQ(1, q2_.size());
    delete n;
    }

    可以看出TEST_F()的使用方法与TEST()差别不大,当以上两个测试实例运行时,Gtest为我们做了以下事情:

    1. 构造一个QueueTest对象(假设为t1);
    2. 调用t1.SetUp()初始化t1对象;
    3. 第一个测试实例(IsEmptyInitially)使用t1进行测试;
    4. 调用t1.TearDown()进行数据清理;
    5. 销毁对象t1;
    6. 创建一个新的QueueTest对象,对下一个测试实例DequeueWorks重复以上步骤。

    可见Gtest通过创建和销毁固件类对象,为每一个测试实例创建了一份独立的初始化数据,上面的两个测试方案的目的和结果完全一样,但方案二通过使用测试固件,杜绝了数据初始化带来的重复代码。


    固件类(Fixture class)

    C++类具有可继承的特点,这样我们可以灵活地定义固件类,我们可以把多个固件类共有的特性抽象出来形成一个基类,以进一步达到代码复用、数据复用的效果,来看下面一个例子。

    class QuickTest : public testing::Test {
    protected:
    // This is a good place to record the start time.
    virtual void SetUp() {
    start_time_ = time(NULL);
    }
    // check if the test was too slow.
    virtual void TearDown() {
    // Gets the time when the test finishes
    const time_t end_time = time(NULL);
    // Asserts that the test took no more than ~5 seconds.
    EXPECT_TRUE(end_time - start_time_ <= 5) << "The test took too long.";
    }

    该固件类对测试实例的运行时间作一个简单的分析,其利用了SetUp()在测试实例运行前执行、TearDown()在测试实例运行后执行的特点,运行时间超过5秒的测试实例将检测失败,注意SetUp()和TearDown()函数中也可以使用断言语句。

    假设我们对Queue类的测试实例有执行时间限制,我们可以编写继承自QuickTest的固件类:

    class QueueTest : public QuickTest {
    //......
    };

    经过这样定义,与QueueTest相关联的测试实例运行时,其执行时间将得到检测。

    小结

    本文介绍了使用Gtest测试固件(Test fixture)的原因及方法,最后提出可以通过类继承的方式灵活定义测试固件。下一节将介绍Gtest值参数化、类型参数化的使用方法。


    Reference: googletest project

  • 相关阅读:
    Java操作XML文件
    数据结构之shell排序
    制作个人开发IDE
    ActiveMQ基本详解与总结
    ActiveMQ的作用总结(应用场景及优势)
    C# 封装miniblink 使用HTML/CSS/JS来构建.Net 应用程序界面和简易浏览器
    已使用.netframework,version=v4.6.1 而不是目标框架netcoreapp,version=v2.1 还原包,此包可能与项目不完全兼容
    RabbitMQ的六种工作模式
    RabbitMQ学习系列
    RabbitMQ基本概念和原理
  • 原文地址:https://www.cnblogs.com/bangerlee/p/2198532.html
Copyright © 2020-2023  润新知