天天看点

Google Test测试框架分析Google Test测试框架分析

Google

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

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

Google Test测试框架分析Google Test测试框架分析

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

<col>

// 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( &amp;argc,

argv ); //

Gtest测试框架初始化

    return

RUN_ALL_TESTS(); //

执行所有测试

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

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

1.

断言

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

断言描述

GTest宏举例

GTest

CppUint

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

ASSERT_TRUE

二元操作断言,断言&gt; &gt;= &lt;= &lt; = !=

ASSERT_GT

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

ASSERT_STREQ

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

ASSERT_THROW

浮点数比较(指定阈值)

ASSERT_FLOAT_EQ

Windows的HRESULT类型

ASSERT_HRESULT_FAILED

断言类型是否相同

StaticAssertTypeEq&lt;T1, T2&gt;

自定义断言

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

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

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

使用内置的boolean函数

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

Fatal assertion

Nonfatal

assertion

Verifies

ASSERT_PRED1(pred1,

val1);

EXPECT_PRED1(pred1,

pred1(val1)returns true

ASSERT_PRED2(pred2, val1,

val2);

EXPECT_PRED2(pred2, val1,

pred2(val1, val2)returns

true

...

使用返回AssertionResult类型的函数

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

使用谓词格式化

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

::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));

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

1. 数据驱动能力

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

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

Test)和类型参数化测试(Type-Parameterized Test)

测试固件类

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

//测试对象ComparePointer

bool

ComparePointer( int

*p1, int

*p2 )

    if(

p1 == p2

        return true;

false;

//测试对象GetOffset

long

int GetOffset( int

* baseAddr , int

* Addr )

(long int)( baseAddr - Addr);

//定义fixture

SimpleTestFixture : public ::testing::Test

protected:

    virtual

void SetUp()

        p1 = new int;

        p2 = new int;

    }

void TearDown()

        delete p1;

        delete p2;

*p1;

*p2;

//使用Fixture定义Test Case

TEST_F( SimpleTestFixture,

TestCase_ComparePointer )

    ASSERT_FALSE(

ComparePointer(p1,p2));

TestCase_GetOffset )

    ASSERT_EQ(

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

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

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

值参数化测试

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

// Type-Parameterized

CTXString_ParamTest : public ::testing::TestWithParam&lt;const WCHAR*&gt;

//参数定义

INSTANTIATE_TEST_CASE_P(InstantiationName,

CTXString_ParamTest,

::testing::Values(

L"XXXXXXX",

L"XLLLLLL", L"X654432"));

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

TEST_P(CTXString_ParamTest,

CTXString_GetLength_Test )

strTest( GetParam());

    ASSERT_EQ(7,strTest.GetLength());

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

CTXString_GetX_Test )

strTest( GetParam()

);

L‘X‘,

strTest.GetAt(0) );

(3)类型参数化测试

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

//测试对象定义

virtual

class SimpleBase{

public:

GetZero();

SimpleTypeA : public

SimpleBase{

GetZero(){

        return 0;

SimpleTypeB : public

SimpleBase {

GetZero() {

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

//定义一个Fixture模板

template

&lt;class T&gt;

SimpleTypeFixture : public testing::Test

    SimpleTypeFixture()

{ ParamPtr = new

T;}

~SimpleTypeFixture() {}

    T

*ParamPtr;

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

typedef

testing::Types&lt;SimpleTypeA, SimpleTypeB&gt;

TypeToPass;

TYPED_TEST_CASE(SimpleTypeFixture, TypeToPass

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

TYPED_TEST(SimpleTypeFixture, GetZeroTest)

0 , this-&gt;ParamPtr-&gt;GetZero() );

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

2. 测试结果

(1)标准测试结果

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

Google Test测试框架分析Google Test测试框架分析

如果需要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多层次来展示。

&lt;?xml version="1.0"

encoding="UTF-8"?&gt;

&lt;testsuites tests="2" failures="0"

disabled="0" errors="0" time="0.015" name="AllTests"&gt;

&lt;testsuite

name="CTXString_GetLength" tests="2" failures="0" disabled="0"

errors="0" time="0"&gt;

&lt;testcase name="CTXString_Test"

status="run" time="0" classname="CTXString_GetLength" /&gt;

&lt;testcase name="CTXString_Test2"

&lt;/testsuite&gt;

&lt;/testsuites&gt;

自定义测试结果

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

RecordProperty("AKey",89);

则最后输出的XML文件为

status="run" time="0" classname="CTXString_GetLength" Akey="89"

/&gt;

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

3. 测试异常

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

支持死亡测试的宏:

ASSERT_DEATH(statement,

regex`);

EXPECT_DEATH(statement,

statementcrashes with the given

error

ASSERT_DEATH_IF_SUPPORTED(statement,

EXPECT_DEATH_IF_SUPPORTED(statement,

if death

tests are supported, verifies that statement crashes with the

given error; otherwise verifies nothing

ASSERT_EXIT(statement, predicate,

EXPECT_EXIT(statement, predicate,

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

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

AbortExit2) {

    EXPECT_EXIT(

AbortExit(), testing::ExitedWithCode(3), "A

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

1. 自定义断言

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

MyOppNumHelper( int

v1, int v2 )

v1+v2

== 0;

#define ASSERT_OPPNUM( v1,

v2 ) \

ASSERT_PRED2( MyOppNumHelper,

v1, v2

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

全局运行环境重载

使用Fixture类可以对每单独一个Test

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

Environment {

// 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() {}

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

MyEnv : public

::testing::Environment

    MyEnv(){}

    ~MyEnv(){}

    void

SetUp(){

        printf("Printf

before GTest begins.\n");

TearDown(){

after GTest terminates.\n");

main(int

argc, char

**argv)

    ::testing::InitGoogleTest(&amp;argc,

argv);

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

RUN_ALL_TESTS();

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

TEST( DoNothingTest,

NothingTest){}

Google Test测试框架分析Google Test测试框架分析

3. 事件机制

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

TestEventListener {

virtual ~TestEventListener() {}

// Fired

before any test activity starts.

virtual void OnTestProgramStart(const

UnitTest&amp; unit_test)

= 0;

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&amp; unit_test,

iteration) = 0;

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

virtual void OnEnvironmentsSetUpStart(const UnitTest&amp; unit_test)

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

virtual void OnEnvironmentsSetUpEnd(const

before the test case starts.

virtual void OnTestCaseStart(const

TestCase&amp; test_case)

before the test starts.

virtual void OnTestStart(const

TestInfo&amp; test_info)

after a failed assertion or a SUCCESS().

virtual void OnTestPartResult(const

TestPartResult&amp; test_part_result) = 0;

after the test ends.

virtual void OnTestEnd(const TestInfo&amp; test_info)

after the test case ends.

virtual void OnTestCaseEnd(const

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

virtual void OnEnvironmentsTearDownStart(const UnitTest&amp; unit_test)

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

virtual void OnEnvironmentsTearDownEnd(const UnitTest&amp; unit_test)

after each iteration of tests finishes.

virtual void OnTestIterationEnd(const

after all test activities have ended.

virtual void OnTestProgramEnd(const

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

MinimalistPrinter : public ::testing::EmptyTestEventListener

    //

Called before a test starts.

void OnTestStart(const ::testing::TestInfo&amp;

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.

void OnTestPartResult(

        const ::testing::TestPartResult&amp;

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.

void OnTestEnd(const ::testing::TestInfo&amp;

Test %s.%s ending.\n",

Gets hold of the event listener list.

    ::testing::TestEventListeners&amp; listeners

=

        ::testing::UnitTest::GetInstance()-&gt;listeners();

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

    listeners.Append(new MinimalistPrinter);

上面的例子实现的就是一个简单的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开始递增分布。

Google Test测试框架分析Google Test测试框架分析

    目前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

Actual: 9

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

    {

        SCOPED_TRACE("XXX");

        FooExpect(3);

        SCOPED_TRACE("YYY");

        FooExpect(9);

    }    

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

Google Test trace:

main.cpp(205): XXX

main.cpp(209): YYY

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

3. 传递严重错误

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

voidSubroutine() {

    ASSERT_EQ(1,

2); // 产生一个错误

    printf("this is

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

Bar) {

    Subroutine();

    int*

p = NULL;

    *p

= 3; // 访问空指针!

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

    ASSERT_NO_FATAL_FAILURE(

Subroutine() );

对于第一种方法,只支持Subroutine()

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

if (HasFatalFailure())

return;

1. 测试框架比较

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

DAT

测试用例

Hard code

基于XML文件的数据驱动

测试结果

内置Console和XML文件输出

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

Console、文本和MFC界面输出

测试方法

xUnit架构,基于断言的方式

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

多线程

内置数据结构非线程安全

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

多机测试

初步支持

不支持(计划支持中)

不支持

框架可扩展性

一般

执行异常处理

提供"死亡测试"

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

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

面向的使用者

测试驱动开发人员

测试驱动开发人员,

测试人员

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

Google Test测试框架分析Google Test测试框架分析

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

GTest对DAT的借鉴意义

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

结合断言的方式和DAT本身的测试方法,在原本使用Log的方式记录的基础上,提供一些断言宏,辅助测试命令的编码。

GTest的死亡测试,可以让使用者轻松的创建一个进程来测试不安全的测试,虽然DAT并不需要死亡测试,但是可以提供一个便利的接口,让DAT的使用者可以轻松的创建一个进程来执行一项测试。

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 {

     CTXString_GetLength_CTXString_Test_NormalStr_Test()

{}

        virtualvoidTestBody();

        static ::testing::TestInfo*

consttest_info_;

//禁止对象复制和赋值

CTXString_GetLength_CTXString_Test_NormalStr_Test(

constCTXString_GetLength_CTXString_Test_NormalStr_Test

&amp;);

        voidoperator= (CTXString_GetLength_CTXString_Test_NormalStr_Testconst &amp;);

//初始化类静态成员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&lt;CTXString_GetLength_CTXString_Test_NormalStr_Test&gt;

//测试用例执行体

voidCTXString_GetLength_CTXString_Test_NormalStr_Test::TestBody()

    CTXStringstrTest(_T("XXXXXXXX"));

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

2. 类型测试

GTest内部广泛对类型测试的支持,这一点一方面是得益于C++类模板机制,我们来看看其中最简单的类型断言(Type

Assertion)。类型断言指的是断言某一个类型与另一个类型是相同的,否则测试在编译时报错。看下面的例子。

template &lt;typenameT&gt; classFoo {

    voidBar() { ::testing::StaticAssertTypeEq&lt;int,

T&gt;(); }

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

//error C2514:

‘testing::internal::StaticAssertTypeEqHelper&lt;T1,T2&gt;‘ : class has

no constructors

voidTest2() { Foo&lt;bool&gt; foo; foo.Bar(); }

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

template &lt;typenameT1, typenameT2&gt;

boolStaticAssertTypeEq() {

internal::StaticAssertTypeEqHelper&lt;T1, T2&gt;();

returntrue;

namespaceinternal {

// This template is

declared, but intentionally undefined.

structStaticAssertTypeEqHelper;

template &lt;typenameT&gt;

structStaticAssertTypeEqHelper&lt;T, T&gt;

{};

} //

namespace internal

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

3. 断言的实现

intSubroutine() {

2); //Line 199

return 0;

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

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&lt;false&gt;::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

AssertHelper(TestPartResult::Typetype,

constchar*

file,

intline,

message);

~AssertHelper();

voidoperator=(constMessage&amp; message)

const; //断言消息流支持

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

AssertHelper( AssertHelper const

voidoperator=(AssertHelper

const &amp;);

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