首页 > 代码库 > 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种方式:
- 使用内置的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/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>
- 自定义测试结果
在以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 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对象的唯一作用就是,将断言的消息填充到消息流中。