说明

被测试代码文件 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

解读sample3的更多相关文章

  1. 解读sample5

    说明 被测试代码文件 sample1.h.sample1.cc和sample3-inl.h 测试代码文件 sample5_unittest.cc 官网上如是描述sample5: Sample #5 i ...

  2. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  3. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  4. SDWebImage源码解读 之 UIImage+GIF

    第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...

  5. SDWebImage源码解读 之 SDWebImageCompat

    第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...

  6. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  7. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

  8. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

  9. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

随机推荐

  1. 读写Excel

    有读Excel,也有生成相同格式的Excel.需要引用Microsoft.Office.Interop.Excel.dll public string ShiPin() { //获取项目下的目录 st ...

  2. Effective Java 学习笔记之第七条——避免使用终结(finalizer)方法

    避免使用终结方法(finalizer) 终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的. 不要把finalizer当成C++中析构函数的对应物.java中,当对象不 ...

  3. SQL批量信息保存(XML格式字符串数据)

    /* *功能:SQL批量信息录入 *此存储过程获取表单信息,插入表中.*/CREATE  PROC [dbo].[sp_SaveToMX1]@XML text   --明细表XML字符串信息ASBEG ...

  4. 【转】iOS6中的Auto Layout:通过代码添加约束

        最近做的项目用到了Auto Layout,于是经过了一番大量的google,这是我看到的讲用代码创建约束最清晰的一篇教程,于是想跟更多的人分享一下.原文也比较简单,可以直接过去看,如果我翻译的 ...

  5. javascript基础学习(十四)

    javascript之表单对象 学习要点: 表单对象 文本框 按钮 单选框和复选框 一.表单对象 在HTML文档中可能会出现多个表单,也就是说,一个HTML文档中可能出现多个<form>标 ...

  6. 【USACO 2.1.2】法雷序列

    [问题描述]     对任意给定的一个自然数 n(n<=160), 将分母小于等于 n 的不可约的真分数按上升的次序排序 , 并且在第一个分数前加上 0/1, 而在最后一个分数后加上 1/1, ...

  7. pip install robotframework-sshlibrary提示: Microsoft Visual C++ 9.0 is required

    win7下 pip install robotframework-sshlibrar时提示: error: Microsoft Visual C++ 9.0 is required (Unable t ...

  8. DropDownList自动生成年月日

    DropDownList自动生成年月日 aspx页面上有三个dropdownlist控件, dropdownlist1 表示年,dropdownlist2表示月,dropdownlist3表示天: 注 ...

  9. excel设置单元格不可编辑

    把允许编辑的单元格选定,右键-设置单元格格式-保护,把锁定前的对钩去掉.再点工具-保护工作表.这样就可以只让你刚才设定的单元格允许编辑,其他不允许.

  10. jdbc 连接mysql Communications link failure的解决办法

    使用Connector/J连接MySQL数据库,程序运行较长时间后就会报以下错误: Communications link failure,The last packet successfully r ...