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

     

  • 相关阅读:
    Study Plan The TwentySecond Day
    Study Plan The Nineteenth Day
    Study Plan The TwentySeventh Day
    Study Plan The Twentieth Day
    Study Plan The TwentyFirst Day
    python实现进程的三种方式及其区别
    yum makecache
    JSONPath 表达式的使用
    oracle执行cmd的实现方法
    php daodb插入、更新与删除数据
  • 原文地址:https://www.cnblogs.com/ubunoon/p/GoogleTestTestAnalysis.html
Copyright © 2011-2022 走看看