zoukankan      html  css  js  c++  java
  • Gooogle Test中的TEST()宏代码分析

    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; }
    };

     

  • 相关阅读:
    AT2165 Median Pyramid Hard
    AT2160 へんなコンパス / Manhattan Compass
    bzoj2863:愤怒的元首
    bzoj5336:[TJOI2018]party
    [luoguP3768]简单的数学题
    bzoj1831:[AHOI2008]逆序对
    bzoj5492:[Hnoi2019]校园旅行
    HNOI2019游记
    HDU 1102 Constructing Roads(kruskal)
    HDU 1059 Dividing
  • 原文地址:https://www.cnblogs.com/ubunoon/p/GoogleTestTestAnalysis.html
Copyright © 2011-2022 走看看