zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    WHERE col1=val1 AND col2=val2;index exists on col1 and col2, the appropriate rows can be fetched directly
    MySQL 交集 实现方法
    MBProgressHUD的使用
    Xcode4 使用 Organizer 分析 Crash logs(转)
    SimpleXML 使用详细例子
    PHP的XML Parser(转)
    iPhone,iPhone4,iPad程序启动画面的总结 (转)
    Pop3得到的Email 信件格式介绍
    yii总结
    隐藏Tabbar的一些方法
  • 原文地址:https://www.cnblogs.com/bangerlee/p/2198532.html
Copyright © 2011-2022 走看看