被测试代码文件 | sample3-inl.h |
测试代码文件 | sample3_unittest.cc |
官网上如是描述sample3:
Sample #3 uses a test fixture.
sample3演示了gtest里面一种叫做test fixture的技术。
sample3的代码文件可以直接添加到前面sample使用的工程中进行编译。
被测试代码里面有两个模版类:
队列是一种常见的数据结构,这里我就不耽搁大家时间了。
直接编译运行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的特性:
某些时候,我们想在不同的test函数执行前创建相同的配置环境,在test函数结束后执行相应的清理工作,test fixture的这种设计恰好提供了这种能力。
test fixture的使用大体可以分成两个部分:
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_F宏有两个参数:
在以下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文档里面找到关于这个问题的解释:
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:
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>