Gtest是google推出的C++测试框架,本篇文档,从整体上对Gtest的运行过程中的关键路径进行分析和梳理。
分析入口
新建一个最简单的测试工程,取名为source_analyse_proj,建立一个简单的测试案例,为了便于分析,可以利用预编译处理器生成经过预编译的文件,来理解复杂宏。具体到VS环境中,进行如下设置:
生成项目,就可以找到对应的main.i预编译处理文件.如果需要生产obj文件,这里面的设置需要都修改为否
整体概览
从使用上,gtest可以看成是由各种不同的TEST宏组合成测试案例,再由RUN_ALL_TESTS()宏来执行各个不同的测试案例,下面来依次分析。
源码:
实际上定义了一个FooTest_HandleNoneZeroInput_Test的类:
可以看出,实际定义的类名称为“测试案例名称_测试名称_Test”,继承于 ::testing::Test类,里面有一个TestBody虚函数,可以猜想到测试框架在具体执行时,肯定是通过TestBody接口去执行各个不同测试案例中的测试函数。
每一个测试案例中都定义了一个静态成员指针,肯定是要在外部进行初始化操作的,这个操作留到下面去讲解。后面两句就是简单的拷贝构造函数和赋值构造函数,这里是为了不允许这两项操作,因此定义为private成员函数.
下面来讨论 test_info_成员,它的初始化时这样的:
利用::testing::internal的全局函数MakeAndRegisterTestInfo来实现初始化,因为它返回的是一个指针,所以可以想到这个函数应该是返回了一个new出来的指针.这里的最后一个输入参数为 new出来的模板实例, TestFactoryImpl提供了创建对应实例的接口,上面的TEST定义了一个类,在这里,生成这个类的模板实例,该类的实例生成需要等到后续执行过程中才会生成,每个测试实例的父类都是::testing::Test
MakeAndRegisterTestInfo的具体实现就下面两句:
这里面,要小析一下GetUnitTestImpl的实现,经过查阅,得知这里是一种将接口定义和细节实现分离的方式,获取主要整理细节代码如下:
UnitTest类中全部成员函数的实现,都是调用UnitTestImpl来实现的,在这里UnitTestImpl是实现,UnitTest是接口,两者之间相互隔离,接口隐藏底层实现细节
好了,我们回到正题,AddTestInfo从名字上面来看,意义应该是增加测试用例信息,这样的用例信息肯定有很多个,UnitTestImpl使用了
来保存每个TestCase信息,每一种TestCase下面可以含有多个测试子用例,所以在TestCase里面,用test_info_list_来保存同属于一个测试用例下面的测试子用例
这上面的一切操作,都是在初始化静态指针成员变量时完成的。
执行所有测试用例
测试框架启动代码,首选接管命令行输入,以便gtest可以通过额外的参数来控制测试流程。
然后调用RUN_ALL_TEST(),运行所有测试用例
这里通过UnitTest::Run进入,然后进入到UnitTestImpl::RunAllTests函数中,为这么这样设计,而不是一开始就进入RunAllTests函数中去呢?这里,gtest考虑到在测试程序执行中,可能会出现一些异常情况,而某些异常可能会导致整个测试进程退出,因此,在UnitTest::Run中,对各个适配平台的异常处理做了设置,确保这些异常不会终止程序,而是被准确记录下来。
测试案例执行流程,移除不相关代码
中间的循环依次执行TestInfo的Run函数,
这里面先会调用TestFactoryBase::CreateTest,这个是通用的虚函数,用于真实new出来测试用例的实例,每一个测试子案例都是派生自::testing::Test类,执行完对应的Run函数后,会通过Test::DeleteSelf_来释放掉对应的控件。Test类的Run函数简化后如下:
依次调用SetUp、TestBody和TearDown函数,其中,TestBody就是我们在TEST宏中真实定义的函数.这里,Gtest用到了特殊的方法来定位TestBody和实际代码入口,从预编译的结果上来看,应该是记录了TEST()宏后面测试语句的所在行号,在实际运行过程中,找到根据这个行号,找到对应的入口地址,然后去执行.具体原理,尚不清楚。
数据共享
全局测试数据共享,可以通过继承testing::Environment得到环境子类,在此子类中定义全局共享数据,在SetUp/TearDown虚函数中进行对应的初始化,启动时将此环境子类添加到全局环境中,就可在每个测试案例中使用。【这种方式,可能会破坏各个测试实例之间的独立性,不建议使用】
测试案例级别数据共享,可以通过继承testing::Test得到测试用例,在此子类中定义静态成员变量,在SetUpTestCase/TearDownTestCase虚函数中进行对应初始化,使用TEST_F来进行测试用例的声明,即可共享数据
参考链接:
http://www.cnblogs.com/jycboy/p/AdvancedGuide2.html
http://www.cnblogs.com/coderzh/archive/2009/04/06/1426755.html