代码改变世界

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

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

 

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()宏,该宏的实现如下(注意不要忽略了\符号):

 

// Helper macro for defining tests.
#define GTEST_TEST(test_case_name, test_name, parent_class)\
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
 
public:\
  GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() 
{}\
 
private:\
  
virtual void TestBody();\
  
static ::testing::TestInfo* const test_info_;\
  GTEST_DISALLOW_COPY_AND_ASSIGN(\
      GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
}
;\
\
::testing::TestInfo
* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
  ::test_info_ 
=\
    ::testing::
internal::MakeAndRegisterTestInfo(\
        #test_case_name, #test_name, 
"""", \
        ::testing::
internal::GetTypeId< parent_class >(), \
        parent_class::SetUpTestCase, \
        parent_class::TearDownTestCase, \
        
new ::testing::internal::TestFactoryImpl<\
            GTEST_TEST_CLASS_NAME_(test_case_name, test_name)
>);\
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))
}

 

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

 



宏定义扩展将得到:

 

FactorialTest_Zero_Test

我们一步步来分析,这个类很简单,一个默认的构造函数实现,一个虚拟函数声明,一个静态变量声明,一个未知的宏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,实现的非常有技巧:

Code


利用编译器只为模板生成一个对象实例,相同类(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; }
};