Gooogle Test中的TEST()宏代码分析

本文深入解析Google Test中的TEST宏的工作原理。从TEST宏定义出发,逐步揭示其内部机制,包括如何定义测试案例、生成测试类及其实现细节,以及如何通过工厂模式创建测试对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Primer文档中了解到,一般情况下,在TEST()宏定义一个自己的测试案例,第一部分为单元测试名字,第二部分为测试名。那么TEST()宏的原定义是一个什么样的形式的呢?为什么只需要定义TEST()宏就可以了呢,这里面有什么技巧吗?作为一个技术员,虽然只需要接口就能够编写应用程序,然而如果能够获取内部更多信息,那么我们将会编写的更加完美的程序,编写的程序更加有效。那么下面我们就慢慢开始解剖TEST()宏。

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

gtest/gtest.h头文件中,可以找到TEST()宏的定义:


 #define TEST(test_case_name, test_name)\
  GTEST_TEST(test_case_name, test_name, ::testing::Test)

也是就是TEST()宏是通过GTEST_TEST宏来实现的,也就是TEST()宏将用GTEST_TEST()来替代。那么既然想要刨根问底,我们就要知道GTEST_TEST()是怎么实现的,我在gtest.h头文件通过查找方式怎么也找不到GTEST_TEST()宏,那么该文件在哪儿呢?

 

Primer并没有提示我们在外部使用GTEST_TEST(),这也就是说GTEST_TEST()应该是内部使用的,否则也不用TEST在来包装一下的。查看gtest目录后断定应该在gtest-internal.h文件中,打开gtest-internal.h文件,查找搜索了一下,嘿嘿,果真在里面有GTEST_TEST()宏,该宏的实现如下(注意不要忽略了\符号):

 

None.gif// Helper macro for defining tests.
None.gif
#define GTEST_TEST(test_case_name, test_name, parent_class)\
ExpandedBlockStart.gifContractedBlock.gif
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class dot.gif{\
InBlock.gif 
public:\
ExpandedSubBlockStart.gifContractedSubBlock.gif  GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() 
dot.gif{}\
InBlock.gif 
private:\
InBlock.gif  
virtual void TestBody();\
InBlock.gif  
static ::testing::TestInfo* const test_info_;\
InBlock.gif  GTEST_DISALLOW_COPY_AND_ASSIGN(\
InBlock.gif      GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
ExpandedBlockEnd.gif}
;\
None.gif\
None.gif::testing::TestInfo
* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
None.gif  ::test_info_ 
=\
None.gif    ::testing::
internal::MakeAndRegisterTestInfo(\
None.gif        #test_case_name, #test_name, 
"""", \
None.gif        ::testing::
internal::GetTypeId< parent_class >(), \
None.gif        parent_class::SetUpTestCase, \
None.gif        parent_class::TearDownTestCase, \
None.gif        
new ::testing::internal::TestFactoryImpl<\
None.gif            GTEST_TEST_CLASS_NAME_(test_case_name, test_name)
>);\
None.gif
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
 

从上面可以看到GTEST_TEST申明了一个类,该类派生自parent_class,类含有一个静态成员变量指针,从TEST宏定义中知道在外部定义TEST()将获取一个从::testing::Test中派生的类申明,类名字为

GTEST_TEST_CLASS_NAME_(test_case_name, test_name),让我们把眼睛往上面瞄一瞄,原来类名字是那么定义的:

// Expands to the name of the class that implements the given test.
#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \
  test_case_name##_##test_name##_Test

 

##表示链接前后字符为字符串。如果我们定义的TEST(FactorialTest, Zero),将会得到一个FactorialTest_Zero_Test的类名。

 

GoogleTestPrimer中宏是被这么定义的:

// Tests factorial of 0.
TEST(FactorialTest, Zero) {
  EXPECT_EQ(
1, Factorial(0))
}

 

很少看到宏定义后面还能与函数类似的,带个括号还能够编写任何语句,通过上面的代码就清楚了,{}中的内容是成员虚拟函数的内容

 

TEST(FactorialTest, Zero)
{
    EXPECT_EQ(
1, Factorial(0));
}


宏定义扩展将得到:

 

ContractedBlock.gifExpandedBlockStart.gifFactorialTest_Zero_Test
class FactorialTest_Zero_Test : public ::testing::Test
{
public:
         FactorialTest_Zero_Test() {}
private:
         
virtual void TestBody();
         
static ::testing::TestInfo* const test_Info_;
         GTEST_DISALLOW_COPY_AND_ASSIGN( FactorialTest_Zero_Test  );
};

::testing::TestInfo
* const FactorialTest_Zero_Test::test_Info_ =
 ::testing::
internal::MakeAndRegisterTestInfo( FactorialTest, Zero, “”, “”, \
         ::testing::
internal::GetTypeId<::testing::Test>(),
         ::testing::Test::SetUpTestCase,
         ::testing::Test::TearDownTestCase,
         
new ::testing::internal::TestFactoryImpl< FactorialTest_Zero_Test> );
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()
{
  EXPECT_EQ(
1, Factorial(0));
}

我们一步步来分析,这个类很简单,一个默认的构造函数实现,一个虚拟函数声明,一个静态变量声明,一个未知的宏GTEST_DISALLOW_COPY_AND_ASSIGN。默认构造函数就不必多说了。先来看虚拟函数,底部的扩展将虚拟函数的实现扩展出来了,前面分析说过,宏TEST()中的内容是虚拟函数中的内容,也就是我们是实现的是虚拟函数TestBody的内容。依据一般的思想,测试内部通过模板设计模式思想来调用该虚拟函数,从而达到TestBody被执行,那么内部如何知道新设计的类呢?那么就需要静态变量testInfo或未知宏来说明了,testInfo通过内部函数MakeAndRegisterTestInfo()调用来实现其指针的赋值。MakeAndRegisterTestInfo()原型如下:


TestInfo
* MakeAndRegisterTestInfo(
    
const char* test_case_name, const char* name,
    
const char* test_case_comment, const char* comment,
    TypeId fixture_class_id,
    SetUpTestCaseFunc set_up_tc,
    TearDownTestCaseFunc tear_down_tc,
TestFactoryBase
* factory);

参数说明:

   test_case_name:             测试案例名称

  name:             测试名称

   test_case_comment: 测试案例注释,将在测试输出中包含

   comment:          测试注释,将在测试输出中包含

   fixture_class_id:     test fixture类的ID

   set_up_tc:          指向构建测试案例的函数指针

   tear_down_tc:     指向销毁测试案例的函数指

   factory:          指向创建测试对象的工厂对象,新创建的TestInfo实例假定属于工厂对象

 

显然前面四个参数毋庸过多的解释,第五个参数test fixtureID,实现的非常有技巧:

ContractedBlock.gifExpandedBlockStart.gifCode
template <typename T>
inline TypeId GetTypeId() {
  
static bool dummy = false;
  
// The compiler is required to create an instance of the static
  
// variable dummy for each T used to instantiate the template.
  
// Therefore, the address of dummy is guaranteed to be unique.
  return &dummy;
}


利用编译器只为模板生成一个对象实例,相同类(

T)仅有一个GetTypeId()函数,而不同类的GetTypeId()函数是不同的,从而,相同的函数具有相同的dummy地址,不同的函数dummy地址不同,从而实现了类的id唯一。没错,代码很简单,但实现的非常perfect。真正应了一句,简单才是最好的!

 

函数地址的指针的赋值实现就相当贵简单了一些,通过传递两个静态实现函数即可,如果需要自己的函数实现,依据Primer教程实现即可!此处暂不分析这部分内容。

 

最后一个是工厂类,显然工厂需要能够生成自定义类,但是工厂并不知道我们定义了什么样的类?那么Google Test是如何使实现的呢,代码是最好的老师:


 // This class provides implementation of TeastFactoryBase interface.该类实现了TestFactoryBase接口
// It is used in TEST and TEST_F macros. 在TEST和TEST_F宏中被实现
template <class TestClass>
class : public TestFactoryBase {
 
public:
  
virtual Test* CreateTest() { return new TestClass; }
};

 


没错,通过模板,新建一个自定义对象,通过虚拟函数返回一个基类的指针,这样
TestFactoryBase就可以控制生成的对象了!

 

我们通过new TestFactoryImpl来得到TestFactoryImpl的实例传递给MakeAndRegisterTestInfo函数,从而实现内部的工厂类管理。内部得到工厂类后调用其虚拟函数CreateTest方法,创建一个新的自定义对象,并管理该对象,由于自定义对象覆盖了其基类的TestBody方法,因此可以通过获得的自定义对象来调用TestBody方法,从而实现了自动测试的目的。

 

上面一段的内容均是猜测,是否如此,我们下回一起分析ALL_TEST_RUNS()宏,看看究竟是否如此。

 

TEST_F()宏的实现与TEST类类似,也在下回一起分析吧。

 

总结一下:

   TEST()宏先扩展成GTEST_TEST()宏,然后GTEST_TEST()宏组装自定义类名字,声明(派生自::testing::Test类)以及部分实现,虚拟函数TestBodyTEST()宏扩展补充实现。在类定义过程中使用模板工厂方法,创建自定义类对象,从而使得内部可以管理自定义类,最终实现测试自动化。

 

其中两个技巧非常不错:

1、通过地址唯一性来获取类ID的唯一性,并且用到了编译器的特性。

template <typename T>
inline TypeId GetTypeId() {
  
static bool dummy = false;
  
return &dummy;
}


2

、通过覆盖基类方法,创建自定义类的实例,采用模板方法,使得实例创建更加简洁,无需更多的Factory类。

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

 

转载于:https://www.cnblogs.com/ubunoon/archive/2008/11/08/GoogleTestTestAnalysis.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值