Google Test(GTest)使用方法和源码解析——预处理技术分析和应用
预处理
在《Google Test(GTest)使用方法和源码解析——概况》最后一部分,我们介绍了GTest的预处理特性。现在我们就详细介绍该特性的使用和相关源码。(转载请指明出于breaksoftware的csdn博客)
测试特例级别预处理
Test Fixtures是建立一个固定/已知的环境状态以确保测试可重复并且按照预期方式运行的装置。通过它,我们可以实现测试特例级别和之后介绍的测试用例级别的预处理逻辑。
举一个比较常见的例子:我们要测试向数据库插入(id,name,location)这样的三个数据,那要先构建一个基础数据(0,Fang,Beijing)。我们第一个测试特例可能需要关注于id这个字段,于是它要在基础数据上做出修改,将(1,Fang,Beijing)插入数据库。第二个测试特例可能需要关注于name字段,于是它要在基础数据上做出修改,将(0,Wang,Beijing)插入数据库。第三个测试特例可能需要关注于location字段,于是它要修改基础数据,将(0,Fang,Nanjing)插入数据库。如果做得鲁莽点,我们在每个测试特例前,先将所有数据填充好,再去操作。但是如果我们将其提炼一下,其实我们发现我们只要在每个特例执行前,获取一份基础数据,然后修改其中本次测试关心的一项就可以了。同时这份基础数据不可以在每个测试特例中被修改——即本次测试特例获取的基础数据不会受之前测试特例对基础数据修改而影响——获取的是一个恒定的数据。
我们看下Test Fixtures类定义及使用规则:
- Test Fixtures类继承于::testing::Test类。
- 在类内部使用public或者protected描述其成员,为了保证实际执行的测试子类可以使用其成员变量(这个我们后面会分析下)
- 在构造函数或者继承于::testing::Test类中的SetUp方法中,可以实现我们需要构造的数据。
- 在析构函数或者继承于::testing::Test类中的TearDown方法中,可以实现一些资源释放的代码(在3中申请的资源)。
- 使用TEST_F宏定义测试特例,其第一个参数要求是1中定义的类名;第二个参数是测试特例名。
其中4这步并不是必须的,因为我们的数据可能不是申请来的数据,不需要释放。还有就是“构造函数/析构函数”和“SetUp/TearDown”的选择,对于什么时候选择哪对,本文就不做详细分析了,大家可以参看https://github.com/google/googletest/blob/master/googletest/docs/FAQ.md#should-i-use-the-constructordestructor-of-the-test-fixture-or-the-set-uptear-down-function。一般来说就是构造/析构函数里忌讳做什么就不要在里面做,比如抛出异常等。
我们以一个例子来讲解
class TestFixtures : public ::testing::Test {
public:TestFixtures() {printf("\nTestFixtures\n");};~TestFixtures() {printf("\n~TestFixtures\n");}
protected:void SetUp() {printf("\nSetUp\n");data = 0;};void TearDown() {printf("\nTearDown\n");}
protected:int data;
};TEST_F(TestFixtures, First) {EXPECT_EQ(data, 0);data = 1;EXPECT_EQ(data, 1);
}TEST_F(TestFixtures, Second) {EXPECT_EQ(data, 0);data = 1;EXPECT_EQ(data, 1);
}
First测试特例中,我们修改了data的数据(23行),第24行验证了修改的有效性和正确性。在second的测试特例中,一开始就检测了data数据(第28行),如果First特例中修改data(23行)影响了基础数据,则本次检测将失败。我们将First和Second测试特例的实现定义成一样的逻辑,可以避免编译器造成的执行顺序不确定从而影响测试结果。我们看下测试输出
[----------] 2 tests from TestFixtures
[ RUN ] TestFixtures.First
TestFixtures
SetUp
TearDown
~TestFixtures
[ OK ] TestFixtures.First (9877 ms)
[ RUN ] TestFixtures.Second
TestFixtures
SetUp
TearDown
~TestFixtures
[ OK ] TestFixtures.Second (21848 ms)
[----------] 2 tests from TestFixtures (37632 ms total)
可以见得,所有局部测试都是正确的,验证了Test Fixtures类中数据的恒定性。我们从输出应该可以看出来,每个测试特例都是要新建一个新的Test Fixtures对象,并在该测试特例结束时销毁它。这样可以保证数据的干净。
我们来看下其实现的源码,首先我们看下TEST_F的实现
#define TEST_F(test_fixture, test_name)\GTEST_TEST_(test_fixture, test_name, test_fixture, \::testing::internal::GetTypeId<test_fixture>())
我们再回顾下在《Google Test(GTest)使用方法和源码解析——自动调度机制分析》中分析的TEST宏的实现
#define GTEST_TEST(test_case_name, test_name)\GTEST_TEST_(test_case_name, test_name, \::testing::Test, ::testing::internal::GetTestTypeId())
可以见得它们的区别就是声明的测试特例类继承于不同的父类。同时使用的是public继承方式,所以子类可以使用父类的public和protected成员。这也是我们在介绍Test Fixtures类编写规则时说的,让使用到的变量置于protected域之下的原因。
#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 {\
我们再看下Test Fixtures类对象在框架中是怎么创建、使用和销毁的。
在TestInfo::Run()函数中有Test Fixtures对象和销毁的代码
// Creates the test object.Test* const test = internal::HandleExceptionsInMethodIfSupported(factory_, &internal::TestFactoryBase::CreateTest,"the test fixture's constructor");// 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();}// Deletes the test object.impl->os_stack_trace_getter()->UponLeavingGTest();internal::HandleExceptionsInMethodIfSupported(test, &Test::DeleteSelf_, "the test fixture's destructor");
因为测试特例类继承于Test Fixtures类,Test Fixtures类继承于Test类,所以我们可以通过厂类生成一个Test类对象的指针,这就是它创建的过程。在测试特例运行结束后,第16~17行将销毁该对象。
在Test类的Run方法中,除了调用了子类定义的虚方法,还执行了SetUp和TearDown方法
internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()");// We will run the test only if SetUp() was successful.if (!HasFatalFailure()) {impl->os_stack_trace_getter()->UponLeavingGTest();internal::HandleExceptionsInMethodIfSupported(this, &Test::TestBody, "the test body");}// However, we want to clean up as much as possible. Hence we will// always call TearDown(), even if SetUp() or the test body has// failed.impl->os_stack_trace_getter()->UponLeavingGTest();internal::HandleExceptionsInMethodIfSupported(this, &Test::TearDown, "TearDown()");
测试用例级别预处理
这种预处理方式也是要使用Test Fixtures。不同的是,我们需要定义几个静态成员:
- 静态成员变量,用于指向数据。
- 静态方法SetUpTestCase()
- 静态方法TearDownTestCase()
举个例子,我们需要自定义测试用例开始和结束时的行为
- 测试开始时输出Start Test Case
- 测试结束时统计结果
class TestFixturesS : public ::testing::Test {
public:TestFixturesS() {printf("\nTestFixturesS\n");};~TestFixturesS() {printf("\n~TestFixturesS\n");}
protected:void SetUp() {};void TearDown() {};static void SetUpTestCase() {UnitTest& unit_test = *UnitTest::GetInstance();const TestCase& test_case = *unit_test.current_test_case();printf("Start Test Case %s \n", test_case.name());};static void TearDownTestCase() {UnitTest& unit_test = *UnitTest::GetInstance();const TestCase& test_case = *unit_test.current_test_case();int failed_tests = 0;int suc_tests = 0;for (int j = 0; j < test_case.total_test_count(); ++j) {const TestInfo& test_info = *test_case.GetTestInfo(j);if (test_info.result()->Failed()) {failed_tests++;}else {suc_tests++;}}printf("End Test Case %s. Suc : %d, Failed: %d\n", test_case.name(), suc_tests, failed_tests);};};TEST_F(TestFixturesS, SUC) {EXPECT_EQ(1,1);
}TEST_F(TestFixturesS, FAI) {EXPECT_EQ(1,2);
}
测试用例中,我们分别测试一个成功结果和一个错误的结果。然后输出如下
[----------] 2 tests from TestFixturesS
Start Test Case TestFixturesS
[ RUN ] TestFixturesS.SUC
TestFixturesS
~TestFixturesS
[ OK ] TestFixturesS.SUC (2 ms)
[ RUN ] TestFixturesS.FAI
TestFixturesS
..\test\gtest_unittest.cc(126): error: Expected: 1
To be equal to: 2
~TestFixturesS
[ FAILED ] TestFixturesS.FAI (5 ms)
End Test Case TestFixturesS. Suc : 1, Failed: 1
[----------] 2 tests from TestFixturesS (12 ms total)
从输出上看,SetUpTestCase在测试用例一开始时就被执行了,TearDownTestCase在测试用例结束前被执行了。我们看下源码中怎么实现的
// Runs every test in this TestCase.
void TestCase::Run() {
......internal::HandleExceptionsInMethodIfSupported(this, &TestCase::RunSetUpTestCase, "SetUpTestCase()");
......for (int i = 0; i < total_test_count(); i++) {GetMutableTestInfo(i)->Run();}
......internal::HandleExceptionsInMethodIfSupported(this, &TestCase::RunTearDownTestCase, "TearDownTestCase()");
......
}
代码之前了无秘密,以上节选的内容可以说明其执行的先后关系以及执行的区域。
全局级别预处理
顾名思义,它是在测试用例之上的一层初始化逻辑。如果我们要使用该特性,则要声明一个继承于::testing::Environment的类,并实现其SetUp/TearDown方法。这两个方法的关系和之前介绍Test Fixtures类是一样的。
我们看一个例子,我们例子中的预处理
- 测试开始时输出Start Test
- 测试结束时统计结果
namespace testing {
namespace internal {
class EnvironmentTest : public ::testing::Environment {
public:EnvironmentTest() {printf("\nEnvironmentTest\n");};~EnvironmentTest() {printf("\n~EnvironmentTest\n");}
public:void SetUp() {printf("\n~Start Test\n");};void TearDown() {UnitTest& unit_test = *UnitTest::GetInstance();for (int i = 0; i < unit_test.total_test_case_count(); ++i) {int failed_tests = 0;int suc_tests = 0;const TestCase& test_case = *unit_test.GetTestCase(i);for (int j = 0; j < test_case.total_test_count(); ++j) {const TestInfo& test_info = *test_case.GetTestInfo(j);// Counts failed tests that were not meant to fail (those without// 'Fails' in the name).if (test_info.result()->Failed()) {failed_tests++;}else {suc_tests++;}}printf("End Test Case %s. Suc : %d, Failed: %d\n", test_case.name(), suc_tests, failed_tests);}};
};
}
}GTEST_API_ int main(int argc, char **argv) {printf("Running main() from gtest_main.cc\n");::testing::AddGlobalTestEnvironment(new testing::internal::EnvironmentTest);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
EnvironmentTest的代码我们就不讲解了,我们可以关注下::testing::AddGlobalTestEnvironment(new testing::internal::EnvironmentTest);这句,我们要在调用RUN_ALL_TESTS之前,使用该函数将全局初始化对象加入到框架中。通过这种方式,可以猜测出,我们可以加入多个对象到框架中。我们看下源码中对它们的调度
bool UnitTestImpl::RunAllTests() {
........ForEach(environments_, SetUpEnvironment);
........// 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();}}
........std::for_each(environments_.rbegin(), environments_.rend(),TearDownEnvironment);
........
}
static void SetUpEnvironment(Environment* env) { env->SetUp(); }
static void TearDownEnvironment(Environment* env) { env->TearDown(); }
截取的源码已经解释的很清楚了。我们看到environments_是个容器,这也印证了我们对于框架中可以有多个Environment的预期。
相关文章:

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

精品软件 推荐 瑞星 杀毒软件 安全软件
一句话评价一下这软件: 功能好,速度一般。功能:设置中心:最后, 下载地址请到官方下载吧。转载于: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所要输入的内容进行过滤控制。按照自己需要过滤,可以自定义,再或者使用定义好的方式。看效果。效果不是很突出,说明下,就是只能输入大写字母和数字。因为加了限制,但是具体有什么高深的应用呢…
Uber最新开源Manifold,助力机器学习开发者的可视化与调试需求
所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项,还有更多福利赠送作者 | Lezhi Li译者 | 凯隐编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导语】2019 年 1 月,Uber 推出了 Manifold,一款与模型无…

jQuery对象和DOM对象使用说明
1.jQuery对象和DOM对象第一次学习jQuery,经常分辨不清哪些是jQuery对象,哪些是 DOM对象,因此需要重点了解jQuery对象和DOM对象以及它们之间的关系.DOM对象,即是我们用传统的方法(javascript)获得的对象,jQuery对象即是用jQuery类库…

[WPF疑难]避免窗口最大化时遮盖任务栏
[WPF疑难]避免窗口最大化时遮盖任务栏 周银辉 WPF窗口最大化时有个很不好的现象是:如果窗口的WindowStyle被直接或间接地设置为None后(比如很多情况下你会覆盖默认的窗体样式,即不采用Windows默认的边框和最大化最等按钮,来打造个…

Google Mock(Gmock)简单使用和源码分析——简单使用
初识Gmock是之前分析GTest源码时,它的源码和GTest源码在同一个代码仓库中(https://github.com/google/googletest)。本文我将以目前最新的Gmock1.7版本为范例,分析其实现原理。(转载请指明出于breaksoftware的csdn博客…
浪潮刘军:为什么说计算力是AI时代“免费的午餐”?
出品 | AI科技大本营(ID:rgznai100)产业AI、元脑生态是浪潮集团2019年度的两大关键词。作为一家以计算力为核心生产力的企业,浪潮还一直强调人工智能计算是未来最重要的计算力,而无论产业AI、元脑生态都构筑于计算的基础设施之上。…

Journey源码分析四:url路由
2019独角兽企业重金招聘Python工程师标准>>> 在入口函数main()的default分支中,对路由进行了注册,现在分析下。 ##main()中路由注册相关代码 源码: httpRouter : httptreemux.New() // Blog and pages as http server.InitializeBlog(httpRou…
“天河二号”总工程师杜云飞谈星光超算应用平台设计
整理 | 夕颜出品 | AI科技大本营(ID:rgznai100)【导读】12 月 21-22 日,OpenI/O 启智开发者大会在深圳召开。在大会上, 国家超级计算广州中心总工程师、“天河二号”总工程师杜云飞发表了题为《星光超算应用平台》的主题报告&…