Google Test(GTest)使用方法和源码解析——Listener技术分析和应用
在《Google Test(GTest)使用方法和源码解析——结果统计机制分析》文中,我么分析了GTest如何对测试结果进行统计的。本文我们将解析其结果输出所使用到的Listener机制。(转载请指明出于breaksoftware的csdn博客)
解析
源码中,我们经常要和UnitTest类打交道。它提供了一个单例方法返回自己的一个对象,然后各处代码都在调用这个单例的方法。所以说它是GTest框架中非常重要的衔接环。而在其内部,实际工作的却是一个UnitTestImpl对象
internal::UnitTestImpl* impl_;
该指针在UnitTest构造函数中被新建出来
UnitTest::UnitTest() {impl_ = new internal::UnitTestImpl(this);
}
之后,我们调用UnitTest单例的方法,很多都是直接调用该对象的方法。其构造函数是这么写的
UnitTestImpl::UnitTestImpl(UnitTest* parent): parent_(parent),GTEST_DISABLE_MSC_WARNINGS_PUSH_(4355 /* using this in initializer */)default_global_test_part_result_reporter_(this),default_per_thread_test_part_result_reporter_(this),GTEST_DISABLE_MSC_WARNINGS_POP_()global_test_part_result_repoter_(&default_global_test_part_result_reporter_),per_thread_test_part_result_reporter_(&default_per_thread_test_part_result_reporter_),.......catch_exceptions_(false) {listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter);
}
本文要讲解的内容将和上面的代码有很大的关系。
在GTest测试框架中,它提出了一个Listener的概念,以供开发者监听执行过程。GTest框架就是使用Listener机制实现了结果输出。我们看下listener基类的设计
class TestEventListener {public:virtual ~TestEventListener() {}// Fired before any test activity starts.virtual void OnTestProgramStart(const UnitTest& unit_test) = 0;// Fired before each iteration of tests starts. There may be more than// one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration// index, starting from 0.virtual void OnTestIterationStart(const UnitTest& unit_test,int iteration) = 0;// Fired before environment set-up for each iteration of tests starts.virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0;// Fired after environment set-up for each iteration of tests ends.virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0;// Fired before the test case starts.virtual void OnTestCaseStart(const TestCase& test_case) = 0;// Fired before the test starts.virtual void OnTestStart(const TestInfo& test_info) = 0;// Fired after a failed assertion or a SUCCEED() invocation.virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0;// Fired after the test ends.virtual void OnTestEnd(const TestInfo& test_info) = 0;// Fired after the test case ends.virtual void OnTestCaseEnd(const TestCase& test_case) = 0;// Fired before environment tear-down for each iteration of tests starts.virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0;// Fired after environment tear-down for each iteration of tests ends.virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0;// Fired after each iteration of tests finishes.virtual void OnTestIterationEnd(const UnitTest& unit_test,int iteration) = 0;// Fired after all test activities have ended.virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0;
};
它暴露了很多接口,每个都对应于执行过程的一个状态,比如OnTestCaseStart,字面意思就是测试用例执行开始处(要执行自定义逻辑),此处比较适合输出测试用例的基本信息;再比如OnTestCaseEnd,是测试用例执行结束处(要执行自定义逻辑),此处比较适合输出测试用例的执行结果。
在UnitTestImpl构造函数中有个listeners()函数,其返回了UnitTestImpl类的TestEventListeners成员变量指针。从名字上看可以看出它是一个Listener的集合,因为用户可以新增自定义的Listener,所以要将其设计为一个集合。但是实际上它只是集合的封装
class GTEST_API_ TestEventListeners {private:.........// The actual list of listeners.internal::TestEventRepeater* repeater_;// Listener responsible for the standard result output.TestEventListener* default_result_printer_;// Listener responsible for the creation of the XML output file.TestEventListener* default_xml_generator_;
}
TestEventRepeater才是Listener的集合,同时它也是继承于TestEventListener接口类。
class TestEventRepeater : public TestEventListener {public:TestEventRepeater() : forwarding_enabled_(true) {}virtual ~TestEventRepeater();void Append(TestEventListener *listener);TestEventListener* Release(TestEventListener* listener);// Controls whether events will be forwarded to listeners_. Set to false// in death test child processes.bool forwarding_enabled() const { return forwarding_enabled_; }void set_forwarding_enabled(bool enable) { forwarding_enabled_ = enable; }virtual void OnTestProgramStart(const UnitTest& unit_test);virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration);virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test);virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test);virtual void OnTestCaseStart(const TestCase& test_case);virtual void OnTestStart(const TestInfo& test_info);virtual void OnTestPartResult(const TestPartResult& result);virtual void OnTestEnd(const TestInfo& test_info);virtual void OnTestCaseEnd(const TestCase& test_case);virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test);virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test);virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);virtual void OnTestProgramEnd(const UnitTest& unit_test);private:// Controls whether events will be forwarded to listeners_. Set to false// in death test child processes.bool forwarding_enabled_;// The list of listeners that receive events.std::vector<TestEventListener*> listeners_;GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventRepeater);
};
这个类的设计也非常有意思,它既在成员变量listeners_中保存了一系列用户自定义的监听者(TestEventListener*对象指针),又让自身继承于TestEventListener,那就是说它自己也是一个Listener。这点比较像大内总管,自己是个太监,手下也是太监。小太监要干的活,大内总管也要能去做(继承于TestEventListener),只是大内总管不用自己亲手去做,而是调度小太监去做。这样的功能传递是通过类似下面代码的调用去实现的
// Since most methods are very similar, use macros to reduce boilerplate.
// This defines a member that forwards the call to all listeners.
#define GTEST_REPEATER_METHOD_(Name, Type) \
void TestEventRepeater::Name(const Type& parameter) { \if (forwarding_enabled_) { \for (size_t i = 0; i < listeners_.size(); i++) { \listeners_[i]->Name(parameter); \} \} \
}
// This defines a member that forwards the call to all listeners in reverse
// order.
#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \
void TestEventRepeater::Name(const Type& parameter) { \if (forwarding_enabled_) { \for (int i = static_cast<int>(listeners_.size()) - 1; i >= 0; i--) { \listeners_[i]->Name(parameter); \} \} \
}
TestEventRepeater从TestEventListener继承来的方法便如此定义
GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest)
GTEST_REPEATER_METHOD_(OnEnvironmentsSetUpStart, UnitTest)
GTEST_REPEATER_METHOD_(OnTestCaseStart, TestCase)
GTEST_REPEATER_METHOD_(OnTestStart, TestInfo)
GTEST_REPEATER_METHOD_(OnTestPartResult, TestPartResult)
GTEST_REPEATER_METHOD_(OnEnvironmentsTearDownStart, UnitTest)
GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsSetUpEnd, UnitTest)
GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsTearDownEnd, UnitTest)
GTEST_REVERSE_REPEATER_METHOD_(OnTestEnd, TestInfo)
GTEST_REVERSE_REPEATER_METHOD_(OnTestCaseEnd, TestCase)
GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest)
可以见得这个大内总管,对于上面的指令只是做了一个简单的判断就抛给下面每个小太监去做了。
说个题外话,个人觉得TestEventRepeater中repeater的翻译,不要叫做“重复者”,叫“中继者”比较好。
我们再回顾下监听者的传递过程
listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter);
void TestEventListeners::SetDefaultResultPrinter(TestEventListener* listener) {if (default_result_printer_ != listener) {// It is an error to pass this method a listener that is already in the// list.delete Release(default_result_printer_);default_result_printer_ = listener;if (listener != NULL)Append(listener);}
}
void TestEventListeners::Append(TestEventListener* listener) {repeater_->Append(listener);
}
SetDefaultResultPrinter方法看着是传递一个“结果打印者”,但是它实际要接受一个Listener。这个命名虽然很直观,但是也让阅读代码的人一下子转不过弯来:Listener和Printer是一回事啊!
class PrettyUnitTestResultPrinter : public TestEventListener {
PrettyUnitTestResultPrinter各个事件处理函数我就不罗列了,它们就是一些结果输出。有兴趣的同学可以自己查看。
然后我们再来看框架中是如何“触发”这些事件的。
首先是UnitTestImpl::RunAllTests()函数,它处理了几个比较大级别的事件,比如程序启动和结束
bool UnitTestImpl::RunAllTests() {......TestEventListener* repeater = listeners()->repeater();......repeater->OnTestProgramStart(*parent_);......for (int i = 0; forever || i != repeat; i++) { ...... repeater->OnTestIterationStart(*parent_, i); ...... if (has_tests_to_run) {// Sets up all environments beforehand.repeater->OnEnvironmentsSetUpStart(*parent_);ForEach(environments_, SetUpEnvironment);repeater->OnEnvironmentsSetUpEnd(*parent_); // Runs the tests only if there was no fatal failure during global// set-up.if (!Test::HasFatalFailure()) {for (int test_index = 0; test_index < total_test_case_count();test_index++) {GetMutableTestCase(test_index)->Run();}} // Tears down all environments in reverse order afterwards.repeater->OnEnvironmentsTearDownStart(*parent_);std::for_each(environments_.rbegin(), environments_.rend(),TearDownEnvironment);repeater->OnEnvironmentsTearDownEnd(*parent_); }...... repeater->OnTestIterationEnd(*parent_, i); ...... }...... repeater->OnTestProgramEnd(*parent_);......
}
然后GetMutableTestCase(test_index)->Run();进入每个测试用例的运行,它只处理了OnTestCaseStart和OnTestCaseEnd两个事件
void TestCase::Run() {...... internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();...... TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();...... repeater->OnTestCaseStart(*this);...... const internal::TimeInMillis start = internal::GetTimeInMillis();for (int i = 0; i < total_test_count(); i++) {GetMutableTestInfo(i)->Run();}...... repeater->OnTestCaseEnd(*this);......
}
GetMutableTestInfo(i)->Run();方法进入测试特例运行,它只处理了OnTestStart和OnTestEnd
void TestInfo::Run() {...... // Tells UnitTest where to store test result.internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();...... TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();// Notifies the unit test event listeners that a test is about to start.repeater->OnTestStart(*this);...... // Runs the test only if the test object was created and its// constructor didn't generate a fatal failure.if ((test != NULL) && !Test::HasFatalFailure()) {// This doesn't throw as all user code that can throw are wrapped into// exception handling code.test->Run();}...... // Notifies the unit test event listener that a test has just finished.repeater->OnTestEnd(*this);......
}
test->Run();进入了我们自定义的测试实体,其内部通过层层传导UnitTestImpl的global_test_part_result_repoter_的函数中
void DefaultGlobalTestPartResultReporter::ReportTestPartResult(const TestPartResult& result) {unit_test_->current_test_result()->AddTestPartResult(result);unit_test_->listeners()->repeater()->OnTestPartResult(result);
}
如此,我们便将listener的运行机制给讲完了。顺便我们也分析了GTest默认结果输出的实现。
应用
要使用Listener技术,我们需要实现一个继承于 testing::TestEventListener 或testing::EmptyTestEventListener的类。如果继承于testing::TestEventListener,则需要我们实现所有纯虚方法;而如果继承于testing::EmptyTestEventListener,则我们只要关注于部分我们关心的函数实现即可——因为它已经把所有纯虚方法实现为空方法了。
class MinimalistPrinter : public ::testing::EmptyTestEventListener {// Called before a test starts.virtual void OnTestStart(const ::testing::TestInfo& test_info) {printf("*** Test %s.%s starting.\n",test_info.test_case_name(), test_info.name());}// Called after a failed assertion or a SUCCEED() invocation.virtual void OnTestPartResult(const ::testing::TestPartResult& test_part_result) {printf("%s in %s:%d\n%s\n",test_part_result.failed() ? "*** Failure" : "Success",test_part_result.file_name(),test_part_result.line_number(),test_part_result.summary());}// Called after a test ends.virtual void OnTestEnd(const ::testing::TestInfo& test_info) {printf("*** Test %s.%s ending.\n",test_info.test_case_name(), test_info.name());}
};
然后我们就需要在main函数中将该Listener的对象加入到框架中。此处有个地方需要注意下,由于Listener是个列表,那就意味着一堆Listener将会被执行,其中包括GTest默认的Listener——之前结果输出的实现者。如果我们只想让我们自定义的Listener执行,则要先将默认Listener去掉(下面代码第3行)。
::testing::TestEventListeners& listeners =::testing::UnitTest::GetInstance()->listeners();delete listeners.Release(listeners.default_result_printer());listeners.Append(new MinimalistPrinter);
这儿有个一个要注意的是:除了OnTestPartResult()之外的函数,都可以使用GTest判断类宏进行数据判断。唯独OnTestPartResult()里不可以,否则会造成OnTestPartResult()被递归调用。
相关文章:

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)【导读】你的五福集齐了吗?作为一名技术人,我们是不是可以…

Google Test(GTest)使用方法和源码解析——模板类测试技术分析和应用
写C难免会遇到模板问题,如果要针对一个模板类进行测试,似乎之前博文中介绍的方式只能傻乎乎的一个一个特化类型后再进行测试。其实GTest提供了两种测试模板类的方法,本文我们将介绍方法的使用,并分析其实现原理。(转载…

IT人才职场受宠
面对就业压力的日益增大,就业难,工资水平低等问题困扰着所有的大学生。然而,IT业的迅猛发展却造成了数以万计的网络设计、运行、维护的网络工程师需求的空缺,巨大的人才缺口使得IT业“全线告急”,这也促使更多的研究人…

引用头文件#include queue出错
为什么80%的码农都做不了架构师?>>> 在工程头文件中引用头文件 #include <queue> 莫名奇妙出错,其原因很可能是由于头文件引用问题。 include/c/4.7.1/bits/stl_vector.h:1308:40: error: expected unqualified-id before ‘(’ token…

ZOJ1002 Fire Net(非递归版)
以前用递归的回溯搜索思路做过一次,参见ZOJ1002 Fire Net(递归版),今天想着用非递归的方法试试看,呵呵,比我想象中要难啊,主要还是堆栈里究竟放什么,这一点上思路一直没理清。因此用了整整一天的时间&#…
“数学不行,干啥也不行”骨灰级程序员:其实你们都是瞎努力
编程圈一直都流传着一个段子:一流程序员靠数学,二流程序员靠算法,末端程序员靠百度,低端看高端就是黑魔法。懂的人其实都知道,这不是段子,其实就是程序员的真实写照。想一想,我们日常学习、求职…

Google Test(GTest)使用方法和源码解析——死亡测试技术分析和应用
死亡测试是为了判断一段逻辑是否会导致进程退出而设计的。这种场景并不常见,但是GTest依然为我们设计了这个功能。我们先看下其应用实例。(转载请指明出于breaksoftware的csdn博客) 死亡测试技术应用 我们可以使用TEST声明并注册一个简单的测…
java学习笔记11--Annotation
java学习笔记11--Annotation Annotation:在JDK1.5之后增加的一个新特性,这种特性被称为元数据特性,在JDK1.5之后称为注释,即:使用注释的方式加入一些程序的信息。 java.lang.annotation Annotation接口是所有的Annotat…

GoogleLog(GLog)源码分析
GLog是Google开发的一套日志输出框架。由于其具有功能强大、方便使用等特性,它被众多开源项目使用。本文将通过分析其源码,解析Glog实现的过程。 该框架的源码在https://github.com/google/glog上可以获取到。本文将以目前最新的0.3.3版本源码为范例进行…

Ajax Toolkit 控件学习系列(13) ——FilteredTextBoxExtender 控制输入
这个控件的作用是对TextBox所要输入的内容进行过滤控制。按照自己需要过滤,可以自定义,再或者使用定义好的方式。看效果。效果不是很突出,说明下,就是只能输入大写字母和数字。因为加了限制,但是具体有什么高深的应用呢…