首页 > 代码库 > Google Test测试框架分析

Google Test测试框架分析

Google Test测试框架分析

一、简介

Google Test是由Google主导的一个开源的C++自动化测试框架,简称GTest。GTest基于xUnit单元测试体系,和CppUint类似,可以看作是JUnit、PyUnit等对C++的移植。

下图是GTest测试框架的测试过程,表示的是GTest的两种测试方式。

下面将使用一个极其简单的例子表示xUnit测试的主要过程。如对Hummer的CTXString类的成员方法GetLength进行测试。详见下面GTest代码和注释说明。

// Gtest使用的头文件

#include "gtest/gtest.h"

 

// 测试对象所需的头文件

#include "Common/Include/Stdtx.h"

#include "Common/Include/TXString.h"

 

// 定义一个CTXString_Test_NormalStr测试用例,并属于CTXSting_GetLength Test Suit

TEST( CTXString_GetLength, CTXString_Test_NormalStr)

{

    CTXString strTest(_T("XXXXXXXX"));

    ASSERT_EQ(8,strTest.GetLength());

 

}

int main( int argc, char **argv )

{

    ::testing::InitGoogleTest( &argc, argv ); // Gtest测试框架初始化

    return RUN_ALL_TESTS(); // 执行所有测试

}

从上面的代码中,可以看出xUint使用的是基于断言的方法,将测试对象执行结果与预期结果进行比较,并得到测试结果。

后续的部分,将从各个方面对GTest测试框架进行分析和评价,并对其部分特性进行分析。

二、GTest测试能力

1. 断言

由于GTest使用的是基于宏定义的方法执行测试命令。故宏断言的使用灵活性是其测试能力的重要标准之一。GTest支持的断言如下表所示:

断言描述

GTest宏举例

GTest

CppUint

基本断言,判断语句为真/假

ASSERT_TRUE

二元操作断言,断言> >= <= < = !=

ASSERT_GT

字符串比较(支持宽字符串)

ASSERT_STREQ

 

断言语句抛出/不抛出特定的异常

ASSERT_THROW

浮点数比较(指定阈值)

ASSERT_FLOAT_EQ

Windows的HRESULT类型

ASSERT_HRESULT_FAILED

 

断言类型是否相同

StaticAssertTypeEq<T1, T2>

 

自定义断言

 

注:CppUint版本为1.10.2,GTest版本为1.5.0。

 

通过上表的比较可以看出GTest支持的断言的方式有多种多样,十分灵活;其中自定义断言的方式更是其亮点,这将在第四部分"可扩展性"介绍。

为了在断言中获取更多的错误信息,GTest提供了以下3种方式:

  1. 使用内置的boolean函数

    如果被测函数返回boolean类型,则可以使用以下断言来获取更多的错误信息

    Fatal assertion

    Nonfatal assertion

    Verifies

    ASSERT_PRED1(pred1, val1);

    EXPECT_PRED1(pred1, val1);

    pred1(val1)returns true

    ASSERT_PRED2(pred2, val1, val2);

    EXPECT_PRED2(pred2, val1, val2);

    pred2(val1, val2)returns true

    ...

    ...

    ...

     

  2. 使用返回AssertionResult类型的函数

    使用GTest提供的::testing::AssertionResult类型来自定义函数,做到输出更详细的错误信息。

  3. 使用谓词格式化

    有些参数不支持<<输出到标准流,那么可以使用自定义谓词格式化函数来输出丰富的错误信息。函数格式如下:

    ::testing::AssertionResult PredicateFormattern(const *expr1, const char*expr2, ... const char*exprn, T1val1, T2val2, ... Tnvaln);

    2. 测试私有代码

    对于一般的接口测试,都基于黑盒测试原则,即只测试其公共接口。我们第一部分的GTest测试流程的左分支和CTXTring测试的例子都是基于这个原则。但是在有些时候,还是需要打破类的封装,对其内部的私有代码进行测试,GTest在设计上也为这种测试提供了便利的途径,看下面的例子。

    // foo.h 测试对象类Foo的头文件

    #include "gtest/gtest_prod.h"

     

    // Defines FRIEND_TEST.

    class Foo {

    private:

        FRIEND_TEST(FooTest, BarReturnsZeroOnNull); // 添加友元测试类

        int Bar(void* x); // 被测试的内部接口

    };

     

    // foo_test.cc 定义测试用例

    TEST(FooTest, BarReturnsZeroOnNull)

    {

        Foo foo;

        EXPECT_EQ(0, foo.Bar(NULL));

    }

     

    三、GTest自动化测试

    对于一个测试自动化框架,主要通过三个方面对其进行评估,分别是数据驱动,测试结果和异常处理。

    1. 数据驱动能力

    xUnit的特点是,对于测试用例的数据,使用与测试对象所使用的一致的程序语言来表示。这样做的优点是,数据的定义方式灵活,特别是抽象数据类型可以在代码中直接定义。但是缺点却很明显:一是测试用例过多的时候,测试数据的定义会增大编码的工作量;二是测试用例维护管理比较麻烦;且每次修改测试用例的数据,都测试程序都需要重新编译。

    对于第一个问题,GTest使用了三种方法来克服,分别是测试固件类(Test Fixture)、值参数化测试(Value-Parameterized Test)和类型参数化测试(Type-Parameterized Test)

  4. 测试固件类

    测试固件类可以使得不同的测试共享相同的测试数据配置。仍然通过一个简单的例子和注释来说明。

    //测试对象ComparePointer

    bool ComparePointer( int *p1, int *p2 )

    {

        if( p1 == p2 )

            return true;

        return false;

    }

    //测试对象GetOffset

    long int GetOffset( int * baseAddr , int * Addr )

    {

        return (long int)( baseAddr - Addr);

    }

     

    //定义fixture

    class SimpleTestFixture : public ::testing::Test {

    protected:

        virtual void SetUp() {

            p1 = new int;

            p2 = new int;

        }

        virtual void TearDown() {

            delete p1;

            delete p2;

        }

        int *p1;

        int *p2;

    };

     

    //使用Fixture定义Test Case

    TEST_F( SimpleTestFixture, TestCase_ComparePointer )

    {

        ASSERT_FALSE( ComparePointer(p1,p2));

    }

    //使用Fixture定义Test Case

    TEST_F( SimpleTestFixture, TestCase_GetOffset )

    {

        ASSERT_EQ( GetOffset(p1,p2) , p1-p2);

    }

    所有的fixture的定义都必须继承::testing::test类,该类其实就是一个接口,定义了SetUp和TearDown接口函数对负责测试用例执行前后环境的生成和撤销。

    Fixture类其实是xUnit处理这类问题的经典的方法,下面介绍的两种的方法则是GTest特有。

  5. 值参数化测试

    值参数化测试和Fixture类的作用类似,也是使得不同的测试共享相同的测试数据配置,但是略有不同。看下面CTXString的例子和注释。

    // Type-Parameterized

    class CTXString_ParamTest : public ::testing::TestWithParam<const WCHAR*>

    {

     

    };

    //参数定义

    INSTANTIATE_TEST_CASE_P(InstantiationName, CTXString_ParamTest,

                         ::testing::Values( L"XXXXXXX", L"XLLLLLL", L"X654432"));

    //使用参数测试CTXString::GetLength

    TEST_P(CTXString_ParamTest, CTXString_GetLength_Test )

    {

        CTXString strTest( GetParam());

        ASSERT_EQ(7,strTest.GetLength());

    }

    //使用参数测试CTXString::GetAt

    TEST_P(CTXString_ParamTest, CTXString_GetX_Test )

    {

        CTXString strTest( GetParam() );

        ASSERT_EQ( L‘X‘, strTest.GetAt(0) );

    }

     

    (3)类型参数化测试

    类型参数化测试,和值参数化测试相似,不同的是用类型来作为参数,故需要使用模板技术来实现。其优点是可以对操作类似或者相同的类型,使用一个Fixture模板来实现所有类的测试用例。看下面的例子和注释。

    //测试对象定义

    virtual class SimpleBase{

    public:

        int GetZero();

    };

     

    class SimpleTypeA : public SimpleBase{

    public:

        int GetZero(){

            return 0;

        }

    };

     

    class SimpleTypeB : public SimpleBase {

    public:

        int GetZero() {

            return 1-1; //仅仅是示例,无视它

        }

    };

     

    //定义一个Fixture模板

    template <class T>

    class SimpleTypeFixture : public testing::Test {

    protected:

        SimpleTypeFixture() { ParamPtr = new T;}

        virtual ~SimpleTypeFixture() {}

        T *ParamPtr;

    };

     

    // 枚举所有需要测试的类型,作为参数,这里两个类型为SimpleTypeA/B

    typedef testing::Types<SimpleTypeA, SimpleTypeB> TypeToPass;

    TYPED_TEST_CASE(SimpleTypeFixture, TypeToPass );

     

    //定义SimpleTypeA/B类GetZero接口的测试用例

    TYPED_TEST(SimpleTypeFixture, GetZeroTest)

    {

        ASSERT_EQ( 0 , this->ParamPtr->GetZero() );

    }

    上面的三种机制可以使得在编写测试程序的时候减少代码量,解决前面提到的第一个问题。但是,GTest目前仍然没有提供一套内置的完整的数据驱动机制,故仍存在测试用例和测试数据维护管理麻烦等问题。

    2. 测试结果

    (1)标准测试结果

    GTest的测试结果支持两种输出方式,Console和XML文件的格式。GTest在默认情况下都是以Console的形式输出;输出的内容包括测试用例的执行结果和时间,以及一个结果总结。下图是一个GTest测试的结果。

     

    如果需要GTest以XML文件的格式输出,必须在执行测试程序的时候增加参数。假设的你的GTest程序名为gtest_test_demo,则下面的例子将测试结果以XML文件的格式输出到C:/TestResult.xml

    gtest_test_demo.exe --gtest_output=xml:C:/TestResult.xml

     

    下图是一个XML输出的例子,同样包括了测试结果和测试时间,而且以Test Suit和Test Case多层次来展示。

    <?xml version="1.0" encoding="UTF-8"?>

    <testsuites tests="2" failures="0" disabled="0" errors="0" time="0.015" name="AllTests">

    <testsuite name="CTXString_GetLength" tests="2" failures="0" disabled="0" errors="0" time="0">

    <testcase name="CTXString_Test" status="run" time="0" classname="CTXString_GetLength" />

    <testcase name="CTXString_Test2" status="run" time="0" classname="CTXString_GetLength" />

    </testsuite>

    </testsuites>

     

  6. 自定义测试结果

    在以XML格式的文件输出结果时,还可以使用RecordProperty函数增加一个键值。如在测试用例中执行

    RecordProperty("AKey",89);

    则最后输出的XML文件为

    <?xml version="1.0" encoding="UTF-8"?>

    <testsuites tests="2" failures="0" disabled="0" errors="0" time="0.015" name="AllTests">

    <testsuite name="CTXString_GetLength" tests="2" failures="0" disabled="0" errors="0" time="0">

    <testcase name="CTXString_Test" status="run" time="0" classname="CTXString_GetLength" />

    <testcase name="CTXString_Test2" status="run" time="0" classname="CTXString_GetLength" Akey="89" />

    </testsuite>

    </testsuites>

     

    GTest还提供了定义测试结果输出方式的结果,详细的介绍在第四部分"可扩展性"。

    3. 测试异常

    在某些测试场合,使用断言的方式执行某一条测试语句的时候,可能会导致测试程序的出现致命的错误,甚至是导致程序崩溃。由于GTest设计的初衷是为了广泛支持个种平台,甚至是异常处理被禁用的系统,故GTest的代码中都没有使用异常处理机制。故,为了保证测试不会由于某条测试语句的异常而退出,GTest提出了一种"死亡测试"的机制,所谓死亡测试,就是只测试本身会导致测试进程异常退出。

    支持死亡测试的宏:

    Fatal assertion

    Nonfatal assertion

    Verifies

    ASSERT_DEATH(statement, regex`);

    EXPECT_DEATH(statement, regex`);

    statementcrashes with the given error

    ASSERT_DEATH_IF_SUPPORTED(statement, regex`);

    EXPECT_DEATH_IF_SUPPORTED(statement, regex`);

    if death tests are supported, verifies that statement crashes with the given error; otherwise verifies nothing

    ASSERT_EXIT(statement, predicate, regex`);

    EXPECT_EXIT(statement, predicate, regex`);

    statementexits with the given error and its exit code matches predicate

    下面以一个例子来展示死亡测试的作用,如要测试一个AbortExit是否按照所设计的一样,让程序异常退出。

    void AbortExit( )

    {

        fprintf(stderr,"A Designed FAIL");

        abort();

    }

    // 第一种方法

    TEST(MyDeathTest, AbortExit1) {

        EXPECT_DEATH( AbortExit(), "A Designed FAIL");

    }

    // 第二种方法,显式制定退出的类型

    TEST(MyDeathTest, AbortExit2) {

        EXPECT_EXIT( AbortExit(), testing::ExitedWithCode(3), "A Designed FAIL");

    }

     

    死亡测试的实现,其实是使用创建进程来执行将要的测试的语句,并最后回收测试进程的执行结果,由于测试进程和GTest进程是互相独立的,故测试进程的崩溃并不会对GTest进程导致不良的影响。

    四、可扩展性

    1. 自定义断言

    GTest提供了一定的接口,可以支持对断言进行一定的设置,也就是提供了一定程度上的自定义断言的机制。如定义一个比较两个数是否是互为相反数:

    bool MyOppNumHelper( int v1, int v2 )

    {

        return v1+v2 == 0;

    }

     

    #define ASSERT_OPPNUM( v1, v2 ) \

    ASSERT_PRED2( MyOppNumHelper, v1, v2 )

     

    这里使用的主要的宏是ASSERT_PRED2,后面的2表示的是宏接受2个变量,同样的存在的宏有ASSERT_PRED1、ASSERT_PRED3、ASSERT_PRED4等等。

  7. 全局运行环境重载

    使用Fixture类可以对每单独一个Test Case的执行环境进行设置,类似的,GTest提供了一个全局环境的设置方法,对应的类由Fixture变为Environment,其定义为

    class Environment {

    public:

    // The d‘tor is virtual as we need to subclass Environment.

    virtual ~Environment() {}

     

    // Override this to define how to set up the environment.

    virtual void SetUp() {}

     

    // Override this to define how to tear down the environment.

    virtual void TearDown() {}

    };

    下面是一个简单的例子说明该类的作用

    class MyEnv : public ::testing::Environment

    {

    public:

        MyEnv(){}

        ~MyEnv(){}

        void SetUp(){

            printf("Printf before GTest begins.\n");

        }

        void TearDown(){

            printf("Printf after GTest terminates.\n");

        }

    };

     

    int main(int argc, char **argv)

    {

        ::testing::InitGoogleTest(&argc, argv);

        ::testing::AddGlobalTestEnvironment(new MyEnv); //注册一个Environment,可以注册多个

        return RUN_ALL_TESTS();

    }

    // 下面定义一个简单的测试用例,如果没有测试用例存在,Environment不会被使用

    TEST( DoNothingTest, NothingTest){}

     

    3. 事件机制

    GTest事件机制提供了一套API可以让使用者获得测试过程进度或者测试失败的情况,并对其响应。事件机制提供了两个类,分别为TestEventListener和EmptyTestEventListener,前者是一个接口类,后者是一个对TestEventListener接口的成员的空操作实现。提供EmptyTestEventListener是为了方便用户,在只需要监听某一个事件时,不必重新实现所有的接口函数。TestEventListener的定义如下,

    class TestEventListener {

    public:

    virtual ~TestEventListener() {}

     

    // Fired before any test activity starts.

    virtual void OnTestProgramStart(const UnitTest& unit_test) = 0;

     

    // Fired before each iteration of tests starts. There may be more than

    // one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration

    // index, starting from 0.

    virtual void OnTestIterationStart(const UnitTest& unit_test,

    int iteration) = 0;

     

    // Fired before environment set-up for each iteration of tests starts.

    virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0;

     

    // Fired after environment set-up for each iteration of tests ends.

    virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0;

     

    // Fired before the test case starts.

    virtual void OnTestCaseStart(const TestCase& test_case) = 0;

     

    // Fired before the test starts.

    virtual void OnTestStart(const TestInfo& test_info) = 0;

     

    // Fired after a failed assertion or a SUCCESS().

    virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0;

     

    // Fired after the test ends.

    virtual void OnTestEnd(const TestInfo& test_info) = 0;

     

    // Fired after the test case ends.

    virtual void OnTestCaseEnd(const TestCase& test_case) = 0;

     

    // Fired before environment tear-down for each iteration of tests starts.

    virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0;

     

    // Fired after environment tear-down for each iteration of tests ends.

    virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0;

     

    // Fired after each iteration of tests finishes.

    virtual void OnTestIterationEnd(const UnitTest& unit_test,

    int iteration) = 0;

     

    // Fired after all test activities have ended.

    virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0;

    };

     

    下面使用一个来自GTest文档的例子说明事件的使用方法和带来的好处。

    class MinimalistPrinter : public ::testing::EmptyTestEventListener {

        // Called before a test starts.

        virtual void OnTestStart(const ::testing::TestInfo& test_info) {

            printf("*** Test %s.%s starting.\n",

                test_info.test_case_name(), test_info.name());

        }

        // Called after a failed assertion or a SUCCEED() invocation.

        virtual void OnTestPartResult(

            const ::testing::TestPartResult& test_part_result) {

                printf("%s in %s:%d\n%s\n",

                    test_part_result.failed() ? "*** Failure" : "Success",

                    test_part_result.file_name(),

                    test_part_result.line_number(),

                    test_part_result.summary());

        }

        // Called after a test ends.

        virtual void OnTestEnd(const ::testing::TestInfo& test_info) {

            printf("*** Test %s.%s ending.\n",

                test_info.test_case_name(), test_info.name());

        }

    };

    int main(int argc, char **argv)

    {

        ::testing::InitGoogleTest(&argc, argv);

     

        // Gets hold of the event listener list.

        ::testing::TestEventListeners& listeners =

            ::testing::UnitTest::GetInstance()->listeners();

        // Adds a listener to the end. Google Test takes the ownership.

        listeners.Append(new MinimalistPrinter);

     

        return RUN_ALL_TESTS();

    }

    TEST( DoNothingTest, NothingTest){}

    上面的例子实现的就是一个简单的GTest结果输出引擎,上面指捕获了三个事件TestStart、TestEnd和TestPartResult,但是由于测试结果就是在这几个时间点上报告,故已经足够了。

    其实,在GTest内部,无论是Console输出和XML结果输出,也是使用事件机制来实现的。使用同样的方法,可以使GTest按照你自己的意愿处理测试结果,如将结果记录到某一个远程数据库或一个本地文本文件。当然,事件机制还远远不止可以做到这些事情。

    五、其他特性

    1. 分布式测试

    GTest支持在多机器上并行执行同一个Test Suit的测试用例。下面用一个简单的例子说明,假设下面的测试用例,包含两个Test Suit:A和B。

    TEST(A, V)
    TEST(A, W)
    TEST(B, X)
    TEST(B, Y)
    TEST(B, Z)

    并假设我们有三台独立的机器,故在每台机器上设置环境变量GTEST_TOTAL_SHARDS制定机器的数目。(GTest将该分布式测试命名为Sharding,并把一个测试机器叫做Shard。)对于不同的Shard,还必须设置另一个环境变量GTEST_SHARD_INDEX;该变量的值每一个Shard都必须不同,且范围是0到GTEST_TOTAL_SHARDS的值减1。

    设置好每个shard的环境变量后,就可以在所有的机器上执行测试。GTest会根据一定的算法,分配测试用例。如,上面的测试用例分配给三个机器可能为这样。

    Machine #0 runs A.V and B.X.

    Machine #1 runs A.W and B.Y.

    Machine #2 runs B.Z.

     

    GTest测试用例的分配目前是以负载均衡为目标,要求在每个Test Suit中的测试用例尽可能的均匀分布。在GTest使用的其实是一个简单的符合均衡分布的哈希函数实现,其实现如下:

    boolShouldRunTestOnShard(inttotal_shards, intshard_index, inttest_id) {

    return (test_id % total_shards) == shard_index;

    }

    ShouldRunTestOnShard是GTest调用用例判断是否执行当前测试用例的函数,total_shards指的是shard的总数,也就是GTEST_TOTAL_SHARDS的值;而shard_index由每个GTest的GTEST_SHARD_INDEX定义。Test_id只得是测试用例的标识符,一般是以0开始递增分布。

        目前GTest的Sharding机制仍处于开发的初步阶段,还有很多问题没有解决,如对于最后的测试结果的报告,GTest甚至没有提供任何汇总的机制,虽然用户本身可以自己实现。

     

    2. 作用域跟踪

    首先看一个简单的测试用例。

    voidFooExpect( intn )

    {

        EXPECT_EQ(1, n);

    //...

    //大量重复的操作

    }

     

    TEST( FooExpectTest, FooTest )

    {

        FooExpect(3);

        FooExpect(9);

    }

    很明显,上面的测试用例会存在两个错误报告,输出结果为

    main.cpp(199): error: Value of: n

    Actual: 3

    Expected: 1

    main.cpp(199): error: Value of: n

    Actual: 9

    Expected: 1

    可以看出的问题是,虽然两个测试失败,但是报告的错误的地点是一样的,这是由于断言是在函数FooExpect中使用。但是如何找到错误时,函数是在哪里被调用?答案就是使用作用域跟踪,将上面的测试用例修改为

    TEST( FooExpectTest, FooTest )

    {

        {

            SCOPED_TRACE("XXX");

            FooExpect(3);

        }

        {

            SCOPED_TRACE("YYY");

            FooExpect(9);

        }    

    }

    执行测试后,结果就变为:

    main.cpp(199): error: Value of: n

    Actual: 3

    Expected: 1

    Google Test trace:

    main.cpp(205): XXX

     

    main.cpp(199): error: Value of: n

    Actual: 9

    Expected: 1

    Google Test trace:

    main.cpp(209): YYY

     

    当然,作用域跟踪还可以作为一个函数跟踪来使用,显然,每一个函数都是一个作用域。在有多重的函数嵌套和调用的时候,使用作用域跟踪也不失为一个好方法。

    3. 传递严重错误

    使用ASSERT的宏断言的机制有一个缺点,就是在错误发生的时候,GTest并没有接受整个错误,而只是中止当前的函数。如下面的例子,在会导致段错误,

    voidSubroutine() {

        ASSERT_EQ(1, 2); // 产生一个错误

        printf("this is not to be executed\n") // 该语句不会被执行

    }

    TEST(FooTest, Bar) {

        Subroutine();

        int* p = NULL;

        *p = 3; // 访问空指针!

    }

    解决这个问题的方法是使用错误传递的方法,错误传递的方法有两种,第一个是使用ASSERT_NO_FATAL_FAILURE。

    TEST(FooTest, Bar) {

        ASSERT_NO_FATAL_FAILURE( Subroutine() );

        int* p = NULL;

        *p = 3; // 访问空指针!

    }

    对于第一种方法,只支持Subroutine() 为当前同一个线程的情况。故,对于不同的线程,可以使用第二种方法,使用HasFatalFailure函数来判断当前的测试中是否有经历过断言失败。

    TEST(FooTest, Bar) {

        Subroutine();

    if (HasFatalFailure()) return;

        int* p = NULL;

        *p = 3; // 访问空指针!

    }

     

  8. GTest、CppUnit和DAT的比较

    1. 测试框架比较

    下表是三个测试框架的比较总汇。

     

    GTest

    DAT

    CppUint

    测试用例

    Hard code

    基于XML文件的数据驱动

    Hard code

    测试结果

    内置Console和XML文件输出

    以TXData结构存储,目前支持XML文件输出,且可以自动产生html总结

    Console、文本和MFC界面输出

    测试方法

    xUnit架构,基于断言的方式

    自定义测试逻辑,使用Log方式记录

    xUnit架构,基于断言的方式

    多线程

    内置数据结构非线程安全

    内置数据结构非线程安全

    内置数据结构非线程安全,但提供一定的多线程保护机制

    多机测试

    初步支持

    不支持(计划支持中)

    不支持

    框架可扩展性

    一般

    一般

    执行异常处理

    提供"死亡测试"

    提供执行超时避免和测试守护进程,支持崩溃自动重启

    可以使用C++异常处理实现

    面向的使用者

    测试驱动开发人员

    测试驱动开发人员,

    测试人员

    测试驱动开发人员

     

    对于上面所述的测试框架,CppUint和GTest由于同属于xUnit测试框架族,故比较类似。DAT则与前者有很大的不同。两者的区别可以用下面的图表示,左边表示的是GTest和CppUint,右边的是DAT。

     

    两种体系的最大的不同是:DAT使用接口封装的方法,令测试模块和测试执行的引擎相对分离,且有更加的数据驱动机制,优点是集成性和维护成本低,适合于持续迭代的测试,但是灵活性略有下降。GTest的测试数据和测试逻辑与测试执行引擎耦合性交高,灵活性高。

  9. GTestDAT的借鉴意义

    GTest和DAT有很大的不同,但是有不少的地方DAT可以从中借鉴并得到改进,包括下面几点:

  10. 结合断言的方式和DAT本身的测试方法,在原本使用Log的方式记录的基础上,提供一些断言宏,辅助测试命令的编码。
  11. GTest的死亡测试,可以让使用者轻松的创建一个进程来测试不安全的测试,虽然DAT并不需要死亡测试,但是可以提供一个便利的接口,让DAT的使用者可以轻松的创建一个进程来执行一项测试。

    七、GTest代码实现技巧浅谈

    1. GTest用例自动注册机制

    如果细心留意第一部分的简单GTest例子,就可以发现只存在两个结构,一个main函数和使用宏辅助实现的测试用例CTXString_Test_NormalStr。也就是说,GTest编写测试用例的时候,并不需要额外进行注册,GTest会自动识别所以的测试用例。让我们看看GTest是怎么实现的,我们把第一部分的TEST( CTXString_GetLength, CTXString_Test_NormalStr)的宏展开,可以得到:

    //测试用例类实现

    classCTXString_GetLength_CTXString_Test_NormalStr_Test : public ::testing::Test {

    public:

         CTXString_GetLength_CTXString_Test_NormalStr_Test() {}

    private:

            virtualvoidTestBody();

            static ::testing::TestInfo* consttest_info_;

    //禁止对象复制和赋值

    CTXString_GetLength_CTXString_Test_NormalStr_Test(

    constCTXString_GetLength_CTXString_Test_NormalStr_Test &);

            voidoperator= (CTXString_GetLength_CTXString_Test_NormalStr_Testconst &);

    };

     

    //初始化类静态成员test_info_

    ::testing::TestInfo* constCTXString_GetLength_CTXString_Test_NormalStr_Test::test_info_ =

    ::testing::internal::MakeAndRegisterTestInfo(

    "CTXString_GetLength", "CTXString_Test_NormalStr", "", "",

    ::testing::internal::GetTestTypeId() ,

    ::testing::Test::SetUpTestCase,

    ::testing::Test::TearDownTestCase,             

    new ::testing::internal::TestFactoryImpl<CTXString_GetLength_CTXString_Test_NormalStr_Test> );

     

    //测试用例执行体

    voidCTXString_GetLength_CTXString_Test_NormalStr_Test::TestBody()

    {

        CTXStringstrTest(_T("XXXXXXXX"));

        ASSERT_EQ(8,strTest.GetLength());

    }

    从展开的宏的代码中可以看出,测试用例类的实现中都设置一个"多余"的静态成员,由于静态成员会在程序初始化阶段被初始化,故甚至在main函数开始执行之前,函数MakeAndRegisterTestInfo会被调用,就如该函数的名称表示的一样,测试用例就是在此处自动注册。可见Google的工程师为GTest的易用性还是下了一番功夫。

    2. 类型测试

    GTest内部广泛对类型测试的支持,这一点一方面是得益于C++类模板机制,我们来看看其中最简单的类型断言(Type Assertion)。类型断言指的是断言某一个类型与另一个类型是相同的,否则测试在编译时报错。看下面的例子。

    template <typenameT> classFoo {

    public:

        voidBar() { ::testing::StaticAssertTypeEq<int, T>(); }

    }

    //简单测试,下面会导致编译时报错

    //error C2514: ‘testing::internal::StaticAssertTypeEqHelper<T1,T2>‘ : class has no constructors

    voidTest2() { Foo<bool> foo; foo.Bar(); }

    下面看看StaticAssertTypeEq是怎么实现的。

    template <typenameT1, typenameT2>

    boolStaticAssertTypeEq() {

    internal::StaticAssertTypeEqHelper<T1, T2>();

    returntrue;

    }

    namespaceinternal {

    // This template is declared, but intentionally undefined.

    template <typenameT1, typenameT2>

    structStaticAssertTypeEqHelper;

     

    template <typenameT>

    structStaticAssertTypeEqHelper<T, T> {};

    } // namespace internal

    从上面代码,其中一个模板类只是声明但是没有定义,故尝试调用的时候就会报错。

    3. 断言的实现

    首先看一个简单的测试用例。

    intSubroutine() {

        ASSERT_EQ(1, 2); //Line 199

    return 0;

    }

    TEST(FooTest, Bar) {

        Subroutine();

    }

    从代码上看,似乎没有什么问题,但是在编译的时候却会报错。

    Error 1: ‘return‘ : cannot convert from ‘void‘ to ‘int‘ : main.cpp(199)

    看似问题应该是由于ASSERT导致,所以我们展开这个宏,得到

    GTEST_AMBIGUOUS_ELSE_BLOCKER_//防止ASSERT宏被嵌套在其他if-else语句中潜在的问题

        if ( const ::testing::AssertionResultgtest_ar

    = ::testing::internal::EqHelper<false>::Compare("1","2",1,2) ) //断言检查逻辑

            ;

        else

            return ::testing::internal::AssertHelper(::testing::TestPartResult::kFatalFailure,

    __FILE__, __LINE__,

    gtest_ar.failure_message() )

    = ::testing::Message();

    代码的重点主要在else分支,也就是断言失败的分支。问题的关键点在AssertHelper类,其声明为

    classGTEST_API_AssertHelper {

    public:

    AssertHelper(TestPartResult::Typetype,

    constchar* file,

    intline,

    constchar* message);

    ~AssertHelper();

    voidoperator=(constMessage& message) const; //断言消息流支持

    private:

    //这里省略部分私有成员

     

    //禁止对象复制和赋值

    AssertHelper( AssertHelper const &);

    voidoperator=(AssertHelper const &);

    };

    只要从上面的代码中就可以看到,由于类重载了赋值操作符,且返回类型为void。所以,很明显,GTest的断言必须存在于返回类型为void的函数中。从代码中还可以知道AssertHelper类对象是不可以复制和被用作于赋值的,故这里的AssertHelper对象的唯一作用就是,将断言的消息填充到消息流中。