Google Test(GTest)使用方法和源码解析——结果统计机制分析
在分析源码之前,我们先看一个例子。以《Google Test(GTest)使用方法和源码解析——概况 》一文中最后一个实例代码为基准,修改最后一个“局部测试”结果为错误。(转载请指明出于breaksoftware的csdn博客)
class ListTest : public testing::Test {protected:virtual void SetUp() {_m_list[0] = 11;_m_list[1] = 12;_m_list[2] = 13;}int _m_list[3];
};
TEST_F(ListTest, FirstElement) {EXPECT_EQ(11, _m_list[0]);
}TEST_F(ListTest, SecondElement) {EXPECT_EQ(12, _m_list[1]);
}TEST_F(ListTest, ThirdElement) {EXPECT_EQ(0, _m_list[2]);
}
然后我们观察其输出,从下面的结果我们可以分析出GTest帮我们统计了:
- 有多少测试用例
- 一个测试用例中有多少测试特例
- 一个测试用例中有多少测试特例成功
- 一个测试用例中有多少测试特例失败
- 失败的原因、位置、期待结果、实际结果
Running main() from gtest_main.cc
[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from ListTest
[ RUN ] ListTest.FirstElement
[ OK ] ListTest.FirstElement (0 ms)
[ RUN ] ListTest.SecondElement
[ OK ] ListTest.SecondElement (0 ms)
[ RUN ] ListTest.ThirdElement
../samples/sample11_unittest.cc:86: FailureExpected: 0
To be equal to: _m_list[2]Which is: 13
[ FAILED ] ListTest.ThirdElement (0 ms)
[----------] 3 tests from ListTest (0 ms total)[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (0 ms total)
[ PASSED ] 2 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] ListTest.ThirdElement1 FAILED TEST
在《Google Test(GTest)使用方法和源码解析——自动调度机制分析》一文中,我们分析了,测试用例对象指针将保存在类UnitTestImpl中
// The vector of TestCases in their original order. It owns the
// elements in the vector.std::vector<TestCase*> test_cases_;
那么结果的统计,肯定也是针对这个vector变量的。实际也是如此,我们在代码中找到如下函数,从函数注释,我们就可以知道其对应于上面输出中那个结果的统计
// Gets the number of successful test cases.
int UnitTestImpl::successful_test_case_count() const {return CountIf(test_cases_, TestCasePassed);
}// Gets the number of failed test cases.
int UnitTestImpl::failed_test_case_count() const {return CountIf(test_cases_, TestCaseFailed);
}// Gets the number of all test cases.
int UnitTestImpl::total_test_case_count() const {return static_cast<int>(test_cases_.size());
}// Gets the number of all test cases that contain at least one test
// that should run.
int UnitTestImpl::test_case_to_run_count() const {return CountIf(test_cases_, ShouldRunTestCase);
}// Gets the number of successful tests.
int UnitTestImpl::successful_test_count() const {return SumOverTestCaseList(test_cases_, &TestCase::successful_test_count);
}// Gets the number of failed tests.
int UnitTestImpl::failed_test_count() const {return SumOverTestCaseList(test_cases_, &TestCase::failed_test_count);
}// Gets the number of disabled tests that will be reported in the XML report.
int UnitTestImpl::reportable_disabled_test_count() const {return SumOverTestCaseList(test_cases_,&TestCase::reportable_disabled_test_count);
}// Gets the number of disabled tests.
int UnitTestImpl::disabled_test_count() const {return SumOverTestCaseList(test_cases_, &TestCase::disabled_test_count);
}// Gets the number of tests to be printed in the XML report.
int UnitTestImpl::reportable_test_count() const {return SumOverTestCaseList(test_cases_, &TestCase::reportable_test_count);
}// Gets the number of all tests.
int UnitTestImpl::total_test_count() const {return SumOverTestCaseList(test_cases_, &TestCase::total_test_count);
}// Gets the number of tests that should run.
int UnitTestImpl::test_to_run_count() const {return SumOverTestCaseList(test_cases_, &TestCase::test_to_run_count);
}
CountIf函数返回符合条件的测试用例个数,SumOverTestCaseList函数返回符合条件的所有测试特例的个数。其实现也非常简单,我们以CountIf为例
template <class Container, typename Predicate>
inline int CountIf(const Container& c, Predicate predicate) {// Implemented as an explicit loop since std::count_if() in libCstd on// Solaris has a non-standard signature.int count = 0;for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it) {if (predicate(*it))++count;}return count;
}
这种写法的一个好处就是我们封装了函数调用迭代器中元素,从而不用到处都是遍历。然后我们将重点放在传入的函数指针,以TestCaseFailed为例
// Returns true iff the test case failed.
static bool TestCaseFailed(const TestCase* test_case) {return test_case->should_run() && test_case->Failed();
}
它和TestCasePassed区别就是将test_case调用的Failed函数变成Passed函数。而TestCase的Passed函数只是对Failed函数取反,所以最终还是调用到Failed中,我们看下其实现
bool Failed() const { return failed_test_count() > 0; }
int TestCase::failed_test_count() const {return CountIf(test_info_list_, TestFailed);
}
可见TestCase测试用例对象最终还是要对其下的测试特例对象指针逐个调用TestFailed
// Returns true iff test failed.static bool TestFailed(const TestInfo* test_info) {return test_info->should_run() && test_info->result()->Failed();}
经过这层传递,最终逻辑运行到TestResult的Failed函数中
bool TestResult::Failed() const {for (int i = 0; i < total_part_count(); ++i) {if (GetTestPartResult(i).failed())return true;}return false;
}
GetTestPartResult获取的一个测试特例中“局部测试”的结果。比如
TEST(IsPrimeTest, Negative) {// This test belongs to the IsPrimeTest test case.EXPECT_FALSE(IsPrime(-1));EXPECT_FALSE(IsPrime(-2));EXPECT_FALSE(IsPrime(INT_MIN));
}
这个测试特例中有三个“局部测试”(3、4和5行)。它们的结果保存在TestResult的(实际上并不是所有情况都保存,我们将在之后分析)
// The vector of TestPartResultsstd::vector<TestPartResult> test_part_results_;
现在我们看到了数据的统计逻辑,接下来我们需要关注源码是如何将结果填充到test_part_results_中的。
在源码中,TestResult只提供了AddTestPartResult方法用于保存“局部测试”结果。而调用该方法的地方只有一处
void DefaultGlobalTestPartResultReporter::ReportTestPartResult(const TestPartResult& result) {unit_test_->current_test_result()->AddTestPartResult(result);unit_test_->listeners()->repeater()->OnTestPartResult(result);
}
其调用逻辑最终会归于如下逻辑
void AssertHelper::operator=(const Message& message) const {UnitTest::GetInstance()->AddTestPartResult(data_->type, data_->file, data_->line,AppendUserMessage(data_->message, message),UnitTest::GetInstance()->impl()->CurrentOsStackTraceExceptTop(1)// Skips the stack frame for this function itself.); // NOLINT
}
到此,我们只要关注于AssertHelper的赋值符就行了。但是事情并不像我们想象的那么简单,甚至我认为GTest在这儿实现有个缺陷。为什么这么说呢?我们搜索完代码,发现该类的赋值符调用只有一处
#define GTEST_MESSAGE_AT_(file, line, message, result_type) \::testing::internal::AssertHelper(result_type, file, line, message) \= ::testing::Message()
调用GTEST_MESSAGE_AT_的地方只有
// Generates a nonfatal failure at the given source file location with
// a generic message.
#define ADD_FAILURE_AT(file, line) \GTEST_MESSAGE_AT_(file, line, "Failed", \::testing::TestPartResult::kNonFatalFailure)
和
#define GTEST_MESSAGE_(message, result_type) \GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type)
对ADD_FAILURE_AT的调用只有一处,且只是在出错时。而对GTEST_MESSAGE_的调用则有三处
#define GTEST_FATAL_FAILURE_(message) \return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure)#define GTEST_NONFATAL_FAILURE_(message) \GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure)#define GTEST_SUCCESS_(message) \GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess)
GTEST_FATAL_FAILURE_和GTEST_NONFATAL_FAILURE_都将在出错时被调用,如EXPECT_EQ在内部是这么调用的
#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)
但是对GTEST_SUCCESS_的调用只有一处
// Generates a success with a generic message.
#define GTEST_SUCCEED() GTEST_SUCCESS_("Succeeded")
GTEST_SUCCEED并不会出现在每个判断的宏中。比如EXPECT_EQ的实现是
#define EXPECT_EQ(val1, val2) \EXPECT_PRED_FORMAT2(::testing::internal:: \EqHelper<GTEST_IS_NULL_LITERAL_(val1)>::Compare, \val1, val2)
EXPECT_PRED_FORMAT2宏中只处理了出错的情况——调用GTEST_NONFATAL_FAILURE_——从而触发AssertHelper的赋值符——将结果保存到“局部测试”结果集合中。而正确的情况下并不会保存结果到“局部测试”结果集中!!但是TestResult计算局部测试个数的函数注释说明它包含了所有情况的结果
// Gets the number of all test parts. This is the sum of the number
// of successful test parts and the number of failed test parts.
int TestResult::total_part_count() const {return static_cast<int>(test_part_results_.size());
}
所以,它的注释是错误的!!只有出错的情况会保存“局部测试”错误结果,或者人为调用GTEST_SUCCEED保存“局部测试”正确结果,而其他情况不保存。我一直觉得test_part_results_保存的数据有点混乱,没有准确的表达其意义。
但是这种混乱的保存为什么不会影响测试结果统计呢?我们再看下TestResult的Failed函数
bool TestResult::Failed() const {for (int i = 0; i < total_part_count(); ++i) {if (GetTestPartResult(i).failed())return true;}return false;
}
当我们没有人为调用GTEST_SUCCEED保存“局部测试”正确结果时,test_part_results_只保存了错误结果。如果没有错误结果,total_part_count函数返回0。而从Failed函数返回false,即没有出错。
到此,我们将结果统计的实现讲完了。
相关文章:
贾扬清感谢信:阿里开源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)【导读】你的五福集齐了吗?作为一名技术人,我们是不是可以…

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(递归版),今天想着用非递归的方法试试看,呵呵,比我想象中要难啊,主要还是堆栈里究竟放什么,这一点上思路一直没理清。因此用了整整一天的时间&#…