Google Test(GTest)使用方法和源码解析——自动调度机制分析
在《Google Test(GTest)使用方法和源码解析——概况 》一文中,我们简单介绍了下GTest的使用和特性。从这篇博文开始,我们将深入代码,研究这些特性的实现。(转载请指明出于breaksoftware的csdn博客)
测试用例的自动保存
当使用一组宏构成测试代码后,我们并没有发现调用它们的地方。GTest框架实际上是通过这些宏,将我们的逻辑保存到类中,然后逐个去执行的。我们先查看TEST宏的实现
#define GTEST_TEST(test_case_name, test_name)\GTEST_TEST_(test_case_name, test_name, \::testing::Test, ::testing::internal::GetTestTypeId())// Define this macro to 1 to omit the definition of TEST(), which
// is a generic name and clashes with some other libraries.
#if !GTEST_DONT_DEFINE_TEST
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
#endif
可见它只是对GTEST_TEST_宏的再次封装。GTEST_TEST_宏不仅要求传入测试用例和测试实例名,还要传入Test类名和其ID。我们将GTEST_TEST_的实现拆成三段分析
// Helper macro for defining tests.
#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\public:\GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\private:\virtual void TestBody();\static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\GTEST_DISALLOW_COPY_AND_ASSIGN_(\GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
};\
\
首先使用宏GTEST_TEST_CLASS_NAME_生成类名。该类暴露了一个空的默认构造函数、一个私有的虚函数TestBody、一个静态变量test_info_和一个私有的赋值运算符(将运算符=私有化,限制类对象的赋值和拷贝行为)。
静态变量test_info的作用非常有意思,它利用”静态变量在程序运行前被初始化“的特性,抢在main函数执行之前,执行一段代码,从而有机会将测试用例放置于一个固定的位置。这个是”自动“保存测试用例的本质所在。
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\::test_info_ =\::testing::internal::MakeAndRegisterTestInfo(\#test_case_name, #test_name, NULL, NULL, \::testing::internal::CodeLocation(__FILE__, __LINE__), \(parent_id), \parent_class::SetUpTestCase, \parent_class::TearDownTestCase, \new ::testing::internal::TestFactoryImpl<\GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
我们先跳过这段代码,看完GTEST_TEST_宏的实现,其最后一行是
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
这行要在类外提供TestBody函数的实现。我们要注意下,这个只是函数的一部分,即它只是包含了函数返回类型、函数名,而真正的函数实体是在TEST宏之后的{}内的,如
TEST(FactorialTest, Zero) {EXPECT_EQ(1, Factorial(0));
}
这段代码最后应该如下,它实际上是测试逻辑的主体。
……
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() {EXPECT_EQ(1, Factorial(0));
}
可以说TEST宏的写法只是一种类函数的写法,而实际它“偷梁换柱”,实现了测试的实体。
我们再看下test_info_的初始化逻辑,它调用了::testing::internal::MakeAndRegisterTestInfo函数。我们先关注下最后一个参数,它是一个模板类,模板是当前类名。同时从名字上看,它也是一个工厂类。该类继承于TestFactoryBase,并实现了CreateTest方法——它只是new出了一个模板类对象,并返回
template <class TestClass>
class TestFactoryImpl : public TestFactoryBase {public:virtual Test* CreateTest() { return new TestClass; }
};
MakeAndRegisterTestInfo函数的实现也非常简单:它new出一个TestInfo类对象,并调用UnitTestImpl单例的AddTestInfo方法,将其保存起来。
TestInfo* MakeAndRegisterTestInfo(const char* test_case_name,const char* name,const char* type_param,const char* value_param,CodeLocation code_location,TypeId fixture_class_id,SetUpTestCaseFunc set_up_tc,TearDownTestCaseFunc tear_down_tc,TestFactoryBase* factory) {TestInfo* const test_info =new TestInfo(test_case_name, name, type_param, value_param,code_location, fixture_class_id, factory);GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);return test_info;
}
AddTestInfo试图通过测试用例名等信息获取测试用例,然后调用测试用例对象去新增一个测试特例——test_info。这样我们在此就将测试用例和测试特例的关系在代码中找到了关联。
GetTestCase(test_info->test_case_name(),test_info->type_param(),set_up_tc,tear_down_tc)->AddTestInfo(test_info);
但是如果第一次调用TEST宏,是不会有测试用例类的,那么其中新建测试用例对象,并保存到UnitTestImpl类单例对象的test_cases_中的逻辑是在GetTestCase函数实现中
TestCase* UnitTestImpl::GetTestCase(const char* test_case_name,const char* type_param,Test::SetUpTestCaseFunc set_up_tc,Test::TearDownTestCaseFunc tear_down_tc) {// Can we find a TestCase with the given name?const std::vector<TestCase*>::const_iterator test_case =std::find_if(test_cases_.begin(), test_cases_.end(),TestCaseNameIs(test_case_name));if (test_case != test_cases_.end())return *test_case;// No. Let's create one.TestCase* const new_test_case =new TestCase(test_case_name, type_param, set_up_tc, tear_down_tc);// Is this a death test case?if (internal::UnitTestOptions::MatchesFilter(test_case_name,kDeathTestCaseFilter)) {++last_death_test_case_;test_cases_.insert(test_cases_.begin() + last_death_test_case_,new_test_case);} else {test_cases_.push_back(new_test_case);}test_case_indices_.push_back(static_cast<int>(test_case_indices_.size()));return new_test_case;
}
正如我们所料,在没有找到测试实例对象指针的情况下,新建了一个TestCase测试用例对象,并将其指针保存到了test_cases_中。如此我们就解释了,测试用例是如何被保存的了。
测试特例的保存
接着上例的分析,如下代码将测试特例信息通过TestCase类的AddTestInfo方法保存起来
GetTestCase(test_info->test_case_name(),test_info->type_param(),set_up_tc,tear_down_tc)->AddTestInfo(test_info);
其中AddTestInfo的实现如下
void TestCase::AddTestInfo(TestInfo * test_info) {test_info_list_.push_back(test_info);test_indices_.push_back(static_cast<int>(test_indices_.size()));
}
可见test_info_list_中保存了测试特例信息。
调度的实现
在之前的测试代码中,我们并没有发现main函数。但是C/C++语言要求程序必须要有程序入口,那Main函数呢?其实GTest为了让我们可以更简单的使用它,为我们编写了一个main函数,它位于src目录下gtest_main.cc文件中
GTEST_API_ int main(int argc, char **argv) {printf("Running main() from gtest_main.cc\n");testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
Makefile文件编译了该文件,并将其链接到可执行文件中。这样我们的程序就有了入口。那么这个main函数又是如何将执行流程引到我们的代码中的呢?代码之前了无秘密。短短的这几行,只有04行才可能是我们的代码入口。(03行将程序入参传递给了Gtest库,从而实现了《Google Test(GTest)使用方法和源码解析——概况》中所述的“选择性测试”)。很显然,它的名字——RUN_ALL_TESTS也暴露了它的功能。我们来看下其实现
inline int RUN_ALL_TESTS() {return ::testing::UnitTest::GetInstance()->Run();
}
它最终调用了UnitTest类的单例(GetInstance)的Run方法。UnitTest类的单例是个很重要的对象,它在源码中各处可见,它是连接各个逻辑的重要一环。我们再看下Run方法的核心实现(去除平台差异后)
return internal::HandleExceptionsInMethodIfSupported(impl(),&internal::UnitTestImpl::RunAllTests,"auxiliary test code (environments or event listeners)") ? 0 : 1;
impl()方法返回了一个UnitTestImpl对象指针impl_,它是在UniTes类的构造函数中生成的(HandleExceptionsInMethodIfSupported函数见《Google Test(GTest)使用方法和源码解析——概况》分析)
UnitTest::UnitTest() {impl_ = new internal::UnitTestImpl(this);
}
UnitTestImpl类的RunAllTest方法中,核心的调度代码只有这几行
for (int test_index = 0; test_index < total_test_case_count(); test_index++) {GetMutableTestCase(test_index)->Run();
}
GetMutableTestCase方法逐个返回UnitTestImpl对象成员变量test_cases_中的元素——各个测试用例对象指针,然后调用测试用例的Run方法。
std::vector<TestCase*> test_cases_;
测试用例类TestCase的Run方法逻辑也是类似的,它将逐个获取其下的测试特例信息,并调用其Run方法
for (int i = 0; i < total_test_count(); i++) {GetMutableTestInfo(i)->Run();}
测试特例的Run方法其核心是
Test* const test = internal::HandleExceptionsInMethodIfSupported(factory_, &internal::TestFactoryBase::CreateTest,"the test fixture's constructor");if ((test != NULL) && !Test::HasFatalFailure()) {test->Run();}
它通过构造函数传入的工厂类对象指针调用其重载的CreateTest方法,new出TEST宏中定义的使用GTEST_TEST_CLASS_NAME_命名(用例名_实例名_TEST)的类(之后称测试用例特例类)的对象指针,然后调用测试用例特例类的父类中的Run方法。由于测试用例特例类继承::testing::Test类后,并没有重载其Run方法,所以其调用的还是Test类的Run方法,而Test类的Run方法实际上只是调用了测试用例特例类重载了的TestBody方法
internal::HandleExceptionsInMethodIfSupported(this, &Test::TestBody, "the test body");
而TestBody就是我们之前在分析TEST宏时讲解通过“偷梁换柱”实现的虚方法。
如此整个调度的流程就分析清楚了。
相关文章:

D3D中简单的截图方法 (转)
【ZT】D3D中简单的截图方法 试了下,果然可以。在渲染完所有东东后(Present之前) 获得BackBuffer表面 然后用D3DX的函数保存 voidScreenShot (char*filename) { IDirect3DSurface9 *tmp NULL; IDirect3DSurface9 *back NULL; //生成固定…
2019年,自动化机器学习AutoML技术还火吗? | BDTC 2019
整理 | 王银出品 | AI科技大本营(ID:rgznai100) 【导读】12 月 5-7 日,由中国计算机学会主办,CCF 大数据专家委员会承办,CSDN、中科天玑协办的中国大数据技术大会(BDTC 2019)在北京长城饭店隆重…

第一次使用51cto博客
阿梅第一次使用51cto博客,以后将学习中的总结写到这里来。加油。转载于:https://blog.51cto.com/hopit/1690465

Google Test(GTest)使用方法和源码解析——结果统计机制分析
在分析源码之前,我们先看一个例子。以《Google Test(GTest)使用方法和源码解析——概况 》一文中最后一个实例代码为基准,修改最后一个“局部测试”结果为错误。(转载请指明出于breaksoftware的csdn博客) class ListTest : publi…
贾扬清感谢信:阿里开源10年,致敬千万开源人
整理 | 夕颜【导读】2019 年 10 月,有人曾根据 www.gharchive.org 的数据整理出一份 2019 年GitHub 开源贡献排行榜,获取 GitHub 2019 年的 PushEvent,通过分析 GitHub 用户提交记录中的邮件地址,分辨其所属组织。从这份榜单上可…

热烈庆祝我国神七发射成功!
热烈庆祝我国神七发射成功!
云计算设计模式(十)——守门员模式
云计算设计模式(十)——守门员模式 通过使用充当客户端和应用程序或服务之间的代理,验证和进行消毒的请求,并将它们之间的请求和数据的专用主机实例保护的应用程序和服务。这可以提供一个额外的安全层,并限制了系统的攻…
“不会Linux,怎么干程序员?”骨灰级工程师:干啥都不行!
说起优秀程序员的必备技能,我想大家都可以说很多,比如:数据结构、算法、数学、编程语言等等。但是,你可能会忽略了每一个程序员都应该掌握的技能:Linux。想一想,我们日常学习、求职、工作场景的中ÿ…
Google Test(GTest)使用方法和源码解析——Listener技术分析和应用
在《Google Test(GTest)使用方法和源码解析——结果统计机制分析》文中,我么分析了GTest如何对测试结果进行统计的。本文我们将解析其结果输出所使用到的Listener机制。(转载请指明出于breaksoftware的csdn博客) 解析 源码中,我们…

SSH连接不上Linux的解决方法
SSH连接不上Linux的解决方法: 连续弄了几次,今天早上终于把SSH连接虚拟机连接不通的问题解决了。 先简单说下概要: 主机装的是XP系统,虚拟机用的是red hat Linux。 我用的是nat连接方式是虚拟机内也能上网。 主机是用的校园内寝室共享上网。 …

熬夜翻译完的PureFTPd配置文件
[url]http://www.chinaunix.net[/url] 作者:jeffwu 发表于:2006-07-08 10:31:58 干了个通宵,一边玩一边把配置文件翻译完了,翻得不好的地方还请各位多多提点,少许不是很明白的地方就留在那了。 鼓励转贴,分发…
挑战NLP、量子计算难题,300多支本科生队伍同场角逐,2020 ASC超算竞赛一触即发...
出品 | AI科技大本营(ID:rgznai100)ASC世界大学生超级计算机竞赛(ASCStudent Supercomputer Challenge)是由中国发起的世界最大规模的大学生超算竞赛,与美国SC、德国ISC并称全球三大超算竞赛,也是目前全球最…

Google Test(GTest)使用方法和源码解析——断言的使用方法和解析
在之前博文的基础上,我们将介绍部分断言的使用,同时穿插一些源码。(转载请指明出于breaksoftware的csdn博客) 断言(Assertions) 断言是GTest局部测试中最简单的使用方法,我们之前博文中举得例子…

精品软件 推荐 硬盘物理序列号修改专家
硬盘物理序列号修改专家不是市面上那些简单修改硬盘驱动器的序列号的东西,而是修改硬盘厂商在烧制时刻录在硬盘盒上的,即(硬盘物理序列号),大约20位字母数字的组合1、可以解决部分软件封用户电脑,导致这台电…
知识图谱实体链接是什么?一份“由浅入深”的综述
作者 | 尼古拉瓦砾来源 | Paperweekly(ID:paperweekly)【导读】这个世界充斥着无数的结构化数据(wiki)和非结构化数据(web),然而,如何将两者有效地集成仍然是个非常困难的问题。本文…

Google Test(GTest)使用方法和源码解析——预处理技术分析和应用
预处理 在《Google Test(GTest)使用方法和源码解析——概况》最后一部分,我们介绍了GTest的预处理特性。现在我们就详细介绍该特性的使用和相关源码。(转载请指明出于breaksoftware的csdn博客) 测试特例级别预处理 Test Fixtures是建立一个固…

出色管理者的时间管理
出色管理者的时间管理不少管理者都有这样的感慨:“忙了一天,也不知道忙了什么,时间还不够用。”其实,只要有效地运用时间,就可以提高工作效率,在相同的时间里做更多的事,而且做得更好࿰…

精品软件 推荐 瑞星 杀毒软件 安全软件
一句话评价一下这软件: 功能好,速度一般。功能:设置中心:最后, 下载地址请到官方下载吧。转载于:https://blog.51cto.com/hangtc/1690981

Google Test(GTest)使用方法和源码解析——自定义输出技术的分析和应用
在介绍自定义输出机制之前,我们先了解下AssertResult类型函数。(转载请指明出于breaksoftware的csdn博客) 在函数中使用AssertionResult AssertionResult只有两种类型: AssertionSuccess()AssertionFailure()要么成功࿰…
五年循环期限已到,我们又要步入“AI寒冬”了吗?
作者 | Sam Shead译者 | Kolen编辑 | 夕颜出品 | AI科技大本营(ID: rgznai100) 【导读】过去的十年对人工智能来说是一个重要的十年,但该领域的研究人员认为该行业即将进入一个新的阶段。 过去几年里,人工智能这项技术的…

相知用心.相爱用情
人如花 一生匆匆而过 不要等到你凋落的时候才去眷恋天空,眷恋蝴蝶爱情是短暂的,但却是美丽的该追求的就去追求吧不要留给自己遗憾,不要让自己美丽的花朵枯萎 人生就象一列急驰的火车 机遇和缘分会让许多素昧平生的乘客在旅途中相遇、相识、相…

Android:problem opening wizard the selected wizard could not be started
直接将Eclipse关掉,重新打开后也许就好了。 如还没好,就执行如下步骤: 1.如果还没有添加ADT,则:Help -> Add New Software -> Add 在“Name”中填入ADT。 2.如果已经安装了ADT,就直接将ADT的地址填写…

Google Test(GTest)使用方法和源码解析——私有属性代码测试技术分析
有些时候,我们不仅要测试类暴露出来的公有方法,还要测试其受保护的或者私有方法。GTest测试框架提供了一种方法,让我们可以测试类的私有方法。但是这是一种侵入式的,会破坏原来代码的结构,所以我觉得还是谨慎使用。&am…
170个新项目,579个活跃代码仓库,Facebook开源年度回顾
作者 | Dmitry Vinnik译者 | 泓礼编辑 | 夕颜出品 | AI科技大本营(ID:rgznai100) 【导读】过去一年对于Facebook的开源工程师来说是繁忙的一年。在2019年,Facebook发布了170个新的开源项目,活跃代码仓库产品达到了579…

“怀才不遇”与“怀才不孕”怎么办?
今天在飞机上闲来无事,翻阅深航的随机杂志。一直以来,我乘的比较多的是南航和深航的杂志。南航的杂志基本上都是广告,没有一点可读性的内容。相反,不知道是不是深航的规模较小的原因,找不到合适的广告主吧,…

《评人工智能如何走向新阶段》后记(再续15)
由AI科技大本营下载自视觉中国170. 清华大学全球产业研究院和百度大学Alpha学院于2020年1月5日发表(人工智能)产业智能化白皮书讨论AI发展情况,应用TUMC模型,从技术和综合应用场景的角度,考察热点技术和场景的AI产业化…

Google Test(GTest)使用方法和源码解析——参数自动填充技术分析和应用
在我们设计测试用例时,我们需要考虑很多场景。每个场景都可能要细致地考虑到到各个参数的选择。比如我们希望使用函数IsPrime检测10000以内字的数字,难道我们要写一万行代码么?(转载请指明出于breaksoftware的csdn博客)…

Linux 指令篇:文件系统--fstab
Linux 指令篇:文件系统-----FSTAB指令:FSTAB使用权限 : 超级使用者 使用方式 : 使用编辑器来修改 /etc/fstab (eg. vi /etc/fstab) 说明 : 存放档案系统与目录结构对应资料的档案 fstab 栏位说明: 第一栏(fs_spec): 实际的 device…

跨平台抓包软件,可以替代Fiddler
2019独角兽企业重金招聘Python工程师标准>>> Zed Attack Proxy (ZAP) 是个强大的跨平台的抓包工具,可以用来替代windows下的Fiddler https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project https://github.com/zaproxy/zaproxy/wiki/Download…
集五福,我用Python
所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项,还有更多福利赠送作者 | Crossin先生编辑 | Jane来源 | Crossin的编程教室(ID:crossincode)【导读】你的五福集齐了吗?作为一名技术人,我们是不是可以…