cvmachine.com - 申博开户网

查找: 您的方位主页 > 网络频道 > 阅览资讯:Gtest/Gmock探求(二)--TEST宏剖析

Gtest/Gmock探求(二)--TEST宏剖析

2019-03-29 12:16:07 来历:www.cvmachine.com 【

刚开端看Gtest/Gmock运用办法的时分,自己写了一些测验代码,能作业,可是总觉得有些笼统。你可能会跟我相同有如下疑问:
o 为什么写了TEST宏,咱们自界说的测验就能被运转
o 为什么MOCK_METHODX系列宏只需求用来声明函数就行了,咱们该怎样界说这些被mock的函数的函数体具体逻辑呢?
o 有些语法一不留意就会写错,比方下面的代码:

EXPECT_CALL(*pManager, findAccountForUser(testing::_))
    .Times(2)
    .WillRepeatedly(testing::Invoke(&helper, &AccountHelper::findAccountForUser));

简略在.Time(2)后边加逗号等等。
咱们将带着上面的问题开端剖析gtest源码。我信任在剖析结束后,上述的问题会有相应的答案,而且届时你再去看gtest/gmock相关的阐明文档时,也不会再那么笼统了。在文档中,我会介绍一些相关的剖析和调试的东西以及运用办法。

好,下面开端解开gtest中TEST宏的奥妙:
咱们先写一个十分简略的测验用例,该用例用来测验C函数库里strcmp函数的功用。代码如下:

// TEST_macro_analysis.cpp
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include <string.h>

TEST(StringMothodTest, strcmp) {
  const char *cmp0 = "hello";
  const char *cmp1 = "hello";
  EXPECT_EQ(strcmp(cmp0, cmp1), 0);
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

这段代码完结了一个TEST宏(实践便是一个test case),依据gtest阐明文档知道,这个宏第一个参数为test case name,第二个参数为test name。Gtest采用了自己界说的专有名词规矩,一个test case能够包括多个test。关于这部分的具体介绍,能够参阅这个页面的第二末节“Beware of the nomenclature”。
上面示例代码中,咱们在TEST宏里自界说的测验代码逻辑为:调用strcmp函数比较cmp0和cmp1两个字符串,并运用EXPECT_EQ宏来希望strcmp函数回来的值为0.
在main函数中,依照gtest标准,先调用::testing::InitGoogleTest函数初始化gtest,然后调用RUN_ALL_TESTS()函数开端履行单元测验。

代码文件途径结构
Gtest/Gmock探求(二)--TEST宏剖析
(源码文件名:Test_macro_analysis.cpp,在文件夹gtest_TEST下,这个文件夹跟gmock-1.7.0在同一级)
之所以要给出代码目录结构,是为了后边编译的时分需求设置头文件索引途径,届时咱们才知道我为啥要那样设置索引途径。
1. 首要编译非优化的gtest。
A. 进入gmock.1.7.0目录,运转./configure。(点击这儿下载gmock1.7.0
这个指令会生成gmock工程的Makefile
B. 去掉Makefile文件中的”-O”优化参数字段(为了咱们调试时能跟进并精准定位到gtest源代码)
有两个Makefile需求修正:gmock-1.7.0/Makefile和:gmock-1.7.0/gtest/Makefile
运用vi翻开Makefile,查找“-O”(运用/-O回车进行查找),留意O是大写的O,将查找到的两处-O2直接删去,终究保存退出(两个Makefile的修正办法是相同的)。
C. 在gmock-1.7.0目录下履行make
2. 编译咱们的测验代码:
cd到gtest_TEST目录,然后履行:

g++ -g TEST_macro_analysis.cpp -o test -lpthread -lc -lm -lrt -lgtest -lgmock -L ../gmock-1.7.0/lib/.libs -L ../gmock-1.7.0/gtest/lib/.libs -I../gmock-1.7.0/include -I../gmock-1.7.0/gtest/include

3. 运转和输出:
首要设置gtest/gmock的库索引途径:

export LD_LIBRARY_PATH=../gmock-1.7.0/lib/.libs:../gmock-1.7.0/gtest/lib/.libs:$LD_LIBRARY_PATH

运转可履行测验程序:
Gtest/Gmock探求(二)--TEST宏剖析

代码逻辑很简略,下面咱们来看看这段简略的代码,gtest对它做了一些什么操作。咱们先将这段代码的宏翻开,剖析一下静态的代码。然后运用gdb跟进程序的运转,看看gtest终究干了些什么。

一、 宏翻开
咱们知道C/C++中的宏本质便是代码替换,而且是在编译之前进行的代码替换。
宏翻开的办法有许多,最直接的办法便是程序员检查代码中宏的界说,然后翻开一个notepad++,手动进行代码替换。老实话,我一开端也尝试了手动将宏翻开,但当我层层替换了四五层今后就抛弃了,由于在gtest源码中,很大部分功用完结都依靠了宏,层层替换翻开后,代码会越来越杂乱,手动翻开就显得越来越费劲。
运用编译器指令来进行宏翻开,指令为:

cpp -E TEST_macro_analysis.cpp -I../gmock-1.7.0/include -I../gmock-1.7.0/gtest/include > macro_expansion.cpp

cpp的-E参数:”Preprocess only; do not compile, assemble or link”,意思是只是预处理,不会编译,汇编或许链接。
然后咱们用notepad++翻开macro_expansion.cpp,然后查找上面单元测验代码中的test case名“StringMothodTest”,假如你的notepad++没有设置主动换行,那么你会看到查找定位到这样的代码:
Gtest/Gmock探求(二)--TEST宏剖析

Note: 为了尽可能多的展现源码信息,我截的图都比较大,可是CSDN博客正文内容宽度有限,所以一些截图被缩放,看不清细节。这时就需求各位看官独自翻开这个图片链接,或许下载到本机看了。

这个类的类名便是用咱们界说的tesat case name,test name再加上_Test后缀拼接而成的。咱们美化一下这段代码,得到如下的类界说代码:
Gtest/Gmock探求(二)--TEST宏剖析

Note Again: 截图图片太大,显现有缩放,请右键翻开图片链接或许下载图片后检查图片细节。

将这部分代码跟咱们最开端的源代码做比照,不难看出上面截图赤色框框内的代码便是我的测验代码中TEST宏翻开后的代码。为了进一步证明这个观念,运用代码IDE东西(我运用的是SlickEdit,一个相似于Visual Studio或许Source Insight的IDE)跳转进入TEST宏的源码界说。留意,跳转的时分你会发现gtest源码中有不止一个TEST界说,在咱们的代码中运用了#include “gtest/gtest.h”,这帮助咱们定位咱们运用的TEST宏界说地点的当地(gtest.h头文件中)。
Gtest/Gmock探求(二)--TEST宏剖析
再跳转进入GTEST_TEST_的界说
Gtest/Gmock探求(二)--TEST宏剖析
截图中GTEST_TEST_CLASS_NAME宏会将test_case_nmae和test_name进行拼接并加上_Test后缀。从这儿就很简略证明了图二中赤色框框里的代码正式TEST宏翻开后出产的。
上图中有一个宏运用办法的技巧:终究TestBody()后边没有任何东西,这便是说,咱们在TEST宏后边附加的{}大括号里的内容,本质上就变成了这个类TestBody函数的函数体。后边咱们会看到测验用例开端运转时,调用的正是TestBody这个函数,而履行的函数体正是咱们在TEST宏后边附加的大括号里边的内容。
OK,这一节终究再对宏翻开做个小结:

  1. 生成了以测验用例名,测验名命名加_Test后缀命名的一个类,这个类承继自gtest的::testing::Test类。
  2. 生成了大局初始化类中static成员变量的代码
  3. 终究保存TestBody函数头,这个奇妙的用法能够将咱们在Test宏后边增加的大括号里的内容替换成TestBody的函数体

二、 Gdb调试跟进
跟进调试的意图是为了了解gtest的TEST机制是怎样的逻辑,它做了哪些操作,终究是怎样履行到咱们界说的TEST测验代码的。
首要,前面咱们进行了代码宏翻开,里边有一个static变量的初始化代码。C++的常识告知咱们这个初始化会在main函数之前履行,所以咱们先在这儿打断点,看看这个初始化终究干了什么。
具体操作和剖析如下图:
Gtest/Gmock探求(二)--TEST宏剖析
1. 运用gdb运转程序
2. 设置函数断点:b ::testing::internal::MakeAndRegisterTestInfo
3. 输入r回车运转到断点处,运用bt指令打出函数调用栈来判别是否是咱们想断的当地。
此刻,键盘按ctrl + X + A,会进入gdb实时显现代码的形式。这个快捷键按完今后,界面如下:
Gtest/Gmock探求(二)--TEST宏剖析
能够看到MakeAndRegisterTestInfo函数里首要结构一个TestInfo目标,然后调用GetUnitTestImpl()->AddTestInfo,依据函数名大致能够猜测是记载下这个TestInfo。
1. 终究一个factory参数:从宏翻开的代码能够看到这个参数的值为new ::testing::internal::TestFactoryImpl< StringMothodTest_strcmp_Test>。这个TestFactoryImpl类完结如下:

template <class TestClass>
class TestFactoryImpl : public TestFactoryBase {
public:
  virtual Test* CreateTest() { 
    return new TestClass; 
  }
};

假如你有C++规划形式的一些认知,就会很简略看出这是一个工厂形式,即在这之后,调用factory->CreateTest便能new出一个StringMothodTest_strcmp_Test类的实例来。
2. TestInfo记载了test case name,test name以及上述的factory。其他参数,咱们暂时疏忽。
3. 跟进AddTestInfo函数
首要GetUnitTestImpl函数会获取出一个static的UnitTest实例(这是规划形式中的单例形式)的私有成员变量internal::UnitTestImpl* impl_,不难猜测这个UnitTest实例便是实践操控单元测验的最顶层的类,而具体的完结是由成员变量impl_完结的。
Gtest/Gmock探求(二)--TEST宏剖析
然后进入GetUnitTestImpl()->AddTestInfo函数,如下:
Gtest/Gmock探求(二)--TEST宏剖析
从上图中也能够看到这个函数也很简略,首要设置了一个原始作业途径original_working_dir_,然后调用:

GetTestCase(test_info->test_case_name(),
      test_info->type_param(),
      set_up_tc,
      tear_down_tc)->AddTestInfo(test_info);

接下来首要履行是GetTestCase,代码如下:
Gtest/Gmock探求(二)--TEST宏剖析
也便是说GetTestCase会取出当时test case name的TestCase实例,假如不存在则分配一个新的并记载它。
然后调用GetTestCase(…)->AddTestInfo(test_info),实践上调用的是TestCase::AddTestInfo
Gtest/Gmock探求(二)--TEST宏剖析
记载了test_info,而且也有一个indices的变量在记载每次的增加(稍后剖析其效果)
至此,大局静态初始化代码”::testing::TestInfo* const StringMothodTest_strcmp_Test ::test_info_”运转结束。小结一下,在这个初始化过程中,会为当时TEST结构一个TestCase,而且将test_Info记载到这个TestCase中,test_Info中记载了test case name,test name,当时这个TEST的类factory,以及别的三个参数信息(咱们暂时不care这仨)。
这儿能够做出一个猜测:程序进入main今后,应该便是从那个大局static UnitTest里遍历一切TestCase,然后履行每个TestCase中的test_info,由于有一个factory在其间,所以很简略new出每个test_info对应的test类,然后履行类的TestBody函数。这样就把一切test case履行完了。
咱们带着这个猜测,持续往下履行。
Gtest/Gmock探求(二)--TEST宏剖析
新设置一个main函数的断点,然后c持续运转,再一步步s指令(step)进入到RUN_ALL_TESTS宏。这儿能够看到实践确实是那个UnitTest单例在操控Run,
UnitTest::Run的代码有一点杂,我这儿只关怀代码是怎样Run到咱们自己界说的test case里的,其他代码暂时疏忽。
这儿大致介绍一下Run的代码:
Gtest/Gmock探求(二)--TEST宏剖析

持续进入(s指令)

Gtest/Gmock探求(二)--TEST宏剖析

就这样,调入了大局单例的函数:UnitTest::UnitTestImpl::RunAllTests(),这个函数里有一段中心代码如下:

for (int test_index = 0; test_index < otal_test_case_count(); test_index++) {
  GetMutableTestCase(test_index)->Run();
}

其间GetMutableTestCase会从test_cases_成员变量中取出一个TestCase指针,然后开端运转TestCase::Run。
TestCase::Run中也有一段相似的中心代码:

for (int i = 0; i < total_test_count(); i++) {
  GetMutableTestInfo(i)->Run();
}

这段代码遍历取出每个testInfo,然后调用testInfo::Run。
在TestInfo::Run函数中,会创立一个Test,然后调用Test::Run函数

// 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();
}

实践上这儿创立的test便是TEST宏翻开后咱们的那个类的实例了,由于咱们那个类是承继自Test类的,而且internal::HandleExceptionsInMethodIfSupported函数具体履行的便是factory_->CreateTest函数。
终究再Test::Run函数中,如下代码:

internal::HandleExceptionsInMethodIfSupported(this, &Test::TestBody, "the test body");

这个函数里终究会履行arg0->*arg1,即履行:this->TestBody,终究进入到咱们TEST宏界说的代码中去
Gtest/Gmock探求(二)--TEST宏剖析
自此,TEST宏悉数剖析结束,暂时抛开比如反常捕捉等逻辑,TEST宏能够简略小结为:
1. 生成一个该测验用例的类
2. 大局初始化static变量,将测验用例类信息记载到大局变量中
3. RUN_ALL_TESTS宏会遍历大局记载的测验信息,生成对应test case类的目标,然后调用它基类的Run函数,终究调用TestBody函数。
上述三个过程利用了C++的static变量特性,工厂和单例规划形式,完结了主动注册测验用例,并办理操控测验用例的运转。

 
 

本文地址:http://www.cvmachine.com/a/question/99877.html
Tags: 探求 Gtest Gmock
修改:申博开户网
关于咱们 | 联络咱们 | 友情链接 | 网站地图 | Sitemap | App | 回来顶部