标签:des c style class blog code
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测试框架进行分析和评价,并对其部分特性进行分析。
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类型,则可以使用以下断言来获取更多的错误信息
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 |
... |
... |
... |
使用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)); } |
对于一个测试自动化框架,主要通过三个方面对其进行评估,分别是数据驱动,测试结果和异常处理。
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) |
并假设我们有三台独立的机器,故在每台机器上设置环境变量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; // 访问空指针! } |
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有很大的不同,但是有不少的地方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 { 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
标签:des c style class blog code
原文地址:http://www.cnblogs.com/reach296/p/3758544.html