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. GTest对DAT的借鉴意义

    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对象的唯一作用就是,将断言的消息填充到消息流中。

     

     

     

     

Google Test测试框架分析,布布扣,bubuko.com

文章来自:http://www.cnblogs.com/reach296/p/3758544.html
© 2021 jiaocheng.bubufx.com  联系我们
ICP备案:鲁ICP备09046678号-3