解读sample3

说明

被测试代码文件 sample3-inl.h
测试代码文件 sample3_unittest.cc

官网上如是描述sample3:

Sample #3 uses a test fixture.

sample3演示了gtest里面一种叫做test fixture的技术。

sample3的代码文件可以直接添加到前面sample使用的工程中进行编译。

理解被测试代码

被测试代码里面有两个模版类:

  1. class QueueNode:面向队列结点的封装
  2. class Queue:面向队列的封装

队列是一种常见的数据结构,这里我就不耽搁大家时间了。

理解测试代码:test fixture简介

直接编译运行sample工程,我们可以从gtest的输出内容中推导出samples3测试代码的组织关系:

被测试的类 class Queue
test fixture QueueTest
  - test name 1 DefaultConstructor:测试默认构造函数
  - test name 2 Dequeue:测试成员函数E* Dequeue()
  - test name 3 Map:测试成员函数Queue* Map(F function)

根据前面所学的知识,如果使用TEST宏来组织代码的话,代码的执行顺序预期会是这样:

sample3_unittest.cc用到了gtest里面的test fixture,这种技术能够让测试代码按照下图的顺序执行:

对比以上两个流程图,不难理解下面两个test fixture的特性:

  1. 在每个test函数执行之前,固定地执行SetUp();
  2. 在每个test函数执行之后,固定地执行TearDown()。

某些时候,我们想在不同的test函数执行前创建相同的配置环境,在test函数结束后执行相应的清理工作,test fixture的这种设计恰好提供了这种能力。

test fixture的使用大体可以分成两个部分:

  1. 编写fixture class;
  2. 使用TEST_F宏。

使用test fixture:编写fixture class

sample3_unittest.cc中的class QueueTest就是一个fixture class,我们可以参考下面步骤来编写一个fixture class:

Step1:fixture class必须继承自gtest的testing::Test,sample3里面的fixture class名为“QueueTest”,其中“Queue”是被测试的类名,这是一种简单明了的命名方式,值得学习

// To use a test fixture, derive a class from testing::Test.
class QueueTest : public testing::Test {
  …… ……
};

Step2:定义各个test函数里都需要用到的数据:

  // Declares the variables your tests want to use.
  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;

Step3:重载virtual void SetUp(),在函数里面添加数据的初始化代码,这里我更建议大家使用“override”关键字:

  // virtual void SetUp() will be called before each test is run.  You 
  // should define it if you need to initialize the varaibles.
  // Otherwise, this can be skipped.
  virtual void SetUp() override {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

Step4:重载virtual void TearDown(),在函数里面添加数据的反初始化代码,如果数据不需要反初始化,那么可以不重载TearDown():

  // virtual void TearDown() will be called after each test is run.
  // You should define it if there is cleanup work to do.  Otherwise,
  // you don't have to provide it.
  //
  // virtual void TearDown() {
  // }

Step5:如果有需要,还可以在QueueTest中定义其他成员函数,这些成员函数是可以在test函数里面直接调用的:

  // A helper function for testing Queue::Map().
  void MapTester(const Queue<int> * q) {
    // Creates a new queue, where each element is twice as big as the
    // corresponding one in q.
    const Queue<int> * const new_q = q->Map(Double);

    // Verifies that the new queue has the same size as q.
    ASSERT_EQ(q->Size(), new_q->Size());

    // Verifies the relationship between the elements of the two queues.
    for ( const QueueNode<int> * n1 = q->Head(), * n2 = new_q->Head();
          n1 != NULL; n1 = n1->next(), n2 = n2->next() ) {
      EXPECT_EQ(2 * n1->element(), n2->element());
    }

    delete new_q;
  }

Step6:最后值得一提的地方是,QueueTest的所有成员都不能是private的,原因在于:

protected:  // You should make the members protected s.t. they can be
             // accessed from sub-classes.

使用test fixture:TEST_F宏

TEST_F宏有两个参数:

  • 参数1:test_fixture,必须是fixture class的名字;
  • 参数2:test_name,含义与TEST宏相同,详情请参考《解读sample1》(链接:http://www.cnblogs.com/duxiuxing/p/4273228.html)。

使用test fixture:其他

在以下sample3_unittest.cc的代码片段中,两个test函数里面都访问了QueueTest的成员变量Queue<int> q0_:

1 TEST_F(QueueTest, DefaultConstructor) {
2   EXPECT_EQ(0u, q0_.Size()); // 此处访问了QueueTest::q0_
3 }
1 TEST_F(QueueTest, Dequeue) {
2   int * n = q0_.Dequeue(); // 此处也访问了QueueTest::q0_
3   EXPECT_TRUE(n == NULL);
4 
5   // …… ……
6 }

这里需要强调的是,两处q0_属于不同的QueueTest实例。gtest的官方文档里面是这么描述的:

Note that different tests in the same test case have different test fixture objects, and Google Test always deletes a test fixture before it creates the next one. Google Test does not reuse the same test fixture for multiple tests. Any changes one test makes to the fixture do not affect other tests.

为了验证这个说法,我给class QueueTest加上了构造函数和析构函数,Debug跟踪调试证明,代码的执行顺序如下图所示:

我把SetUp()的代码都移到构造函数里面去,把TearDown()的代码都移到析构函数里面去,经验证,sample3的单元测试代码也是能够正常跑起来的。

于是乎我们会提出这样的疑问:Should I use the constructor/destructor of the test fixture or the set-up/tear-down function?

对于gtest的文档完善性,在此表示崇高的敬意,我们可以在gtest官方的FAQ文档里面找到关于这个问题的解释:

Should I use the constructor/destructor of the test fixture or the set-up/tear-down function?

The first thing to remember is that Google Test does not reuse the same test fixture object across multiple tests. For each TEST_F, Google Test will create a fresh test fixture object, immediately call SetUp(), run the test, call TearDown(), and then immediately delete the test fixture object. Therefore, there is no need to write a SetUp() or TearDown() function if the constructor or destructor already does the job.

You may still want to use SetUp()/TearDown() in the following cases:

  • If the tear-down operation could throw an exception, you must use TearDown() as opposed to the destructor, as throwing in a destructor leads to undefined behavior and usually will kill your program right away. Note that many standard libraries (like STL) may throw when exceptions are enabled in the compiler. Therefore you should prefer TearDown() if you want to write portable tests that work with or without exceptions.
  • The assertion macros throw an exception when flag --gtest_throw_on_failure is specified. Therefore, you shouldn't use Google Test assertions in a destructor if you plan to run your tests with this flag.
  • In a constructor or destructor, you cannot make a virtual function call on this object. (You can call a method declared as virtual, but it will be statically bound.) Therefore, if you need to call a method that will be overriden in a derived class, you have to use SetUp()/TearDown().

不应该被忽略的注释

sample3的解读,上面啰啰嗦嗦讲了这么多,但我感觉还是需要把sample3_unittest.cc的这段注释补充上来才完整:

// In this example, we use a more advanced feature of Google Test called
// test fixture.
//
// A test fixture is a place to hold objects and functions shared by
// all tests in a test case.  Using a test fixture avoids duplicating
// the test code necessary to initialize and cleanup those common
// objects for each test.  It is also useful for defining sub-routines
// that your tests need to invoke a lot.
//
// <TechnicalDetails>
//
// The tests share the test fixture in the sense of code sharing, not
// data sharing.  Each test is given its own fresh copy of the
// fixture.  You cannot expect the data modified by one test to be
// passed on to another test, which is a bad idea.
//
// The reason for this design is that tests should be independent and
// repeatable.  In particular, a test should not fail as the result of
// another test's failure.  If one test depends on info produced by
// another test, then the two tests should really be one big test.
//
// The macros for indicating the success/failure of a test
// (EXPECT_TRUE, FAIL, etc) need to know what the current test is
// (when Google Test prints the test result, it tells you which test
// each failure belongs to).  Technically, these macros invoke a
// member function of the Test class.  Therefore, you cannot use them
// in a global function.  That's why you should put test sub-routines
// in a test fixture.
//
// </TechnicalDetails>

 


系列文章索引:http://www.cnblogs.com/duxiuxing/p/4270836.html

posted @ 2015-02-11 15:52  杜修杏  阅读(796)  评论(0编辑  收藏  举报