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" // TEST( CTXString_GetLength, { CTXString ASSERT_EQ(8,strTest.GetLength()); } int { ::testing::InitGoogleTest( &argc, return } |
从上面的代码中,可以看出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种方式:
- 使用内置的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
...
...
...
- 使用返回AssertionResult类型的函数
使用GTest提供的::testing::AssertionResult类型来自定义函数,做到输出更详细的错误信息。
- 使用谓词格式化
有些参数不支持<<输出到标准流,那么可以使用自定义谓词格式化函数来输出丰富的错误信息。函数格式如下:
::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) - 测试固件类
测试固件类可以使得不同的测试共享相同的测试数据配置。仍然通过一个简单的例子和注释来说明。
//测试对象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特有。
- 值参数化测试
值参数化测试和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/Btypedef
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>
- 自定义测试结果
在以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等等。
- 全局运行环境重载
使用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; // 访问空指针!}
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的测试数据和测试逻辑与测试执行引擎耦合性交高,灵活性高。
- GTest对DAT的借鉴意义
GTest和DAT有很大的不同,但是有不少的地方DAT可以从中借鉴并得到改进,包括下面几点:
- 结合断言的方式和DAT本身的测试方法,在原本使用Log的方式记录的基础上,提供一些断言宏,辅助测试命令的编码。
- 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 constructorsvoidTest2() { 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 199return 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