说明

被测试代码文件 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. 3 Ways to Preload Images with CSS, JavaScript, or Ajax---reference

    Preloading images is a great way to improve the user experience. When images are preloaded in the br ...

  2. Log4j使用教程 (转载http://www.codeceo.com/article/log4j-usage.html)

    日志是应用软件中不可缺少的部分,Apache的开源项目log4j是一个功能强大的日志组件,提供方便的日志记录.在apache网站:jakarta.apache.org/log4j 可以免费下载到Log ...

  3. maven 启动忽略test

    两种方法 1,--命令 mvn install -Dmaven.test.skip=true 2.pom.xml 文件 在tomcat 下面的pom.xml 文件里面加上如下 <!--  ski ...

  4. Servlet学习--练习示例总结

    醉了醉了..本来想测试下Servlet生命周期的,然后调了好久的错误,还是没成功,不知道为什么不能这样做 贴上代码: import java.io.IOException; import java.i ...

  5. MySQL+PHP配置 Windows系统IIS版(转)

    1.下载 MySQL下载地址:http://dev.mysql.com/downloads/mysql/5.1.html->Windows (x86, 32-bit), MSI Installe ...

  6. Android-SVN

    服务器启动svn服务 svnserve -d -r /home/wbp/svn/actia/ 1 .svn  重新定位location , 改变新仓库的uuid , 今天操作SVN Client 发现 ...

  7. NSUserDefaults 很详细的介绍使用(转发)

    http://my.oschina.net/u/1245365/blog/294449

  8. React 点击删除列表中对应项(React 获取DOM中自定义属性)

    点击删除按钮,删除列表中对应项本来是React比较基础的应用,可是应用情况变得复杂了以后,我还真想了一会儿才搞定. 简化一下应用场景:点击新增按钮,增加一条输入框,点击输入框旁边的按钮,删除该输入框( ...

  9. 从头开始学c++,补基础,补踏实

    在对c++一知半解的情况下,写c++程序是非常吃力的.对于半路出家写c++的我,写了几个颓废的程序后,再也没有勇气用现有的c++知识去写千疮百孔的程序.非常想写出<整洁的代码>中那样的代码 ...

  10. 02_ Windows与Linux双重引导

    1. Grub2引导window. ---------------------步骤1--------------------------------- vim /etc/grub.d/40_custo ...