Google Test是Google C++ Testing Framework的一种非正式的称谓,是google最近发布的一个开源C++测试框架。具有以下特点:

Google's framework for writing C++ tests on a variety of platforms (Linux, Mac OS X, Windows, Cygwin, Windows CE, and Symbian). Based on the xUnit architecture. Supports automatic test discovery, a rich set of assertions, user-defined assertions, death tests, fatal and non-fatal failures, type-parameterized tests, various options for running the tests, and XML test report generation.


Google
的测试框架是在不同平台上(LinuxMac OS XWindowsCygwinWindows CESymbian)为编写C++测试而生成的。它是基于xUnit架构的测试框架,支持自动发现测试,丰富的断言集,用户定义的断言,death测试,致命与非致命的失败,类型参数化测试,各类运行测试的选项和XML的测试报告。

更多可参考它的主页:

http://code.google.com/p/googletest/

补充说明:
       对比cppunit,boost.test,Google Test使用更加简便。

      cppunit的mfc输出需要修改好些地方,头文件以及实现文件均需要修改,并且在不同的地方使用不同的宏名称,需要包含不同的头文件,比较繁琐。
 
      boost.test继承boost库的传统,虽然没有仔细使用过,但是从文档中可以看到还是比较复杂的。

     google test相对比较容易,可能为了符合JUnit移植过来的思想:程序员在最简单的情况下就能够使用JUnit,不用修改现有代码,也不用去了解很多库相关的内容以及语言特性。

    google test的单元测试仅需要TEST()和TEST_F()两个宏,真的佩服它的简洁。


使用Google C++ Testing Framework进行C++单元测试

安装:

下载Google C++ Testing Framework,解压...
VC2005:
    直接打开msvc\gtest.vcproj或msvc\gtest.sln,直接编译即可。
Linux/UnixGCC4.0:
    传统过程:./configure  make
Mingw:
BCC:
    用Mingw和BCB6编译需要修改一些代码,文章最后我会把修改过的文件放上来。

使用:

首先#include <gtest/gtest.h>,当然工程的头文件路径要设置正确

1.简单测试TEST

假如我写了个函数,是计算阶乘的:

  1. int Factorial( int n )
  2. {
  3.     if(n==2) return 100; //故意出个错,嘻嘻
  4.     return n<=0? 1 : n*Factorial(n - 1);
  5. }
  6.  
  7. /******************************************************
  8. *TEST:定义一次测试
  9. *第一个参数是测试用例名,第二个参数是测试名
  10. *随后的测试结果将以"测试用例名.测试名"的形式给出
  11. *******************************************************/
  12. TEST(TestFactorial, ZeroInput) 
  13. {
  14.     //EXPECT_EQ稍候再说,现在只要知道它是测试两个数据是否相等的就行了。
  15.     EXPECT_EQ(1, Factorial(0));
  16. }
  17.  
  18. TEST(TestFactorial, OtherInput)
  19. {
  20.     EXPECT_EQ(1, Factorial(1));
  21.     EXPECT_EQ(2, Factorial(2));
  22.     EXPECT_EQ(6, Factorial(3));
  23.     EXPECT_EQ(40320, Factorial(8));
  24.  
  25. int main(int argc, TCHAR* argv[])
  26. {
  27.     //用来处理Test相关的命令行开关,如果不关注也可不加
  28.     testing::InitGoogleTest(&argc,argv); 
  29.  
  30.     //看函数名就知道干啥了
  31.     int r = RUN_ALL_TESTS();  
  32.  
  33.     //只是让它暂停而已,不然一闪就没了
  34.     std::cin.get(); 
  35.  
  36.     //如果全部通过则返回0,否则返回1
  37.     return r;
  38. }
  39. //---------------------------------------------------------------------------

运行结果:

测试框架指出:TestFactorial.ZeroInput运行OK,运行OtherInput时出现三次结果和预期不符。

2.多个测试场景需要相同数据配置的情况,用TEST_F

  1. //用TEST_F做同配置的系列测试
  2. typedef std::basic_string<TCHAR> tstring;
  3. struct FooTest : testing::Test {
  4.     //这里定义要测试的东东
  5.     tstring strExe;
  6.     //可以利用构造、析构来初始化一些参数
  7.     FooTest() {}
  8.     virtual ~FooTest() {}
  9.  
  10.     //如果构造、析构还不能满足你,还有下面两个虚拟函数
  11.     virtual void SetUp() {
  12.         // 在构造后调用
  13.         strExe.resize(MAX_PATH);
  14.         GetModuleFileName(NULL, &strExe[0], MAX_PATH);
  15.     }
  16.  
  17.     virtual void TearDown() { }   // 在析构前调用
  18. };
  19.  
  20. //偶写的从完整路径里取出文件名的函数,待测试(路径分隔符假定为'\\')
  21. tstring getfilename(const tstring &full)  
  22. {
  23.     return full.substr(full.rfind(_T('\\')));
  24. }
  25.  
  26. //偶写的从完整路径里取出路径名的函数,待测试(Windows路径)
  27. tstring getpath(const tstring &full) 
  28. {
  29.     return full.substr(0, full.rfind(_T('\\')));
  30. }
  31.  
  32. TEST_F(FooTest, Test_GFN) 
  33. {
  34.     //测试getfilename函数
  35.     EXPECT_STREQ(_T("Project1.exe"), getfilename(strExe).c_str());
  36. }
  37.  
  38. TEST_F(FooTest, Test_GP) 
  39. {
  40.     //测试getpath函数
  41.     EXPECT_STREQ(
  42.             _T("D:\\Code\\libs\\google\\gtest-1.2.1\\BCC_SPC\\bcc\\ex"),
  43.             getpath(strExe).c_str());
  44. }
  45.  
  46. int main(int argc, TCHAR* argv[])  
  47. {
  48.     //主函数还是一样地
  49.     testing::InitGoogleTest(&argc,argv);
  50.     int r = RUN_ALL_TESTS();
  51.     std::cin.get();
  52.     return r;
  53. }

运行结果:

瞧,Google C++ 测试框架毫不客气地指出偶的getfilename返回的字符串比预期的多了一个'\\'

快速入门:

Google Test提供了两种断言形式,一种以ASSERT_开头,另一种以EXPECT_开头,它们的区别是ASSERT_*一旦失败立马退出,而EXPECT_*还能继续下去。

断言列表:

真假条件测试:

致命断言 非致命断言 验证条件
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition为真
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition 为假

数据对比测试:

致命断言 非致命断言 验证条件
ASSERT_EQ(期望值, 实际值); EXPECT_EQ(期望值, 实际值); 期望值 == 实际值
ASSERT_NE(val1, val2); EXPECT_NE(val1, val2); val1 != val2
ASSERT_LT(val1, val2); EXPECT_LT(val1, val2); val1 < val2
ASSERT_LE(val1, val2); EXPECT_LE(val1, val2); val1 <= val2
ASSERT_GT(val1, val2); EXPECT_GT(val1, val2); val1 > val2
ASSERT_GE(val1, val2); EXPECT_GE(val1, val2); val1 >= val2

字符串(针对C形式的字符串,即char*或wchar_t*)对比测试:

致命断言 非致命断言 验证条件
ASSERT_STREQ(expected_str, actual_str); EXPECT_STREQ(expected_str, actual_str); 两个C字符串有相同的内容
ASSERT_STRNE(str1, str2); EXPECT_STRNE(str1, str2); 两个C字符串有不同的内容
ASSERT_STRCASEEQ(expected_str, actual_str); EXPECT_STRCASEEQ(expected_str, actual_str); 两个C字符串有相同的内容,忽略大小写
ASSERT_STRCASENE(str1, str2); EXPECT_STRCASENE(str1, str2); 两个C字符串有不同的内容,忽略大小写

TEST宏:

TEST宏的作用是创建一个简单测试,它定义了一个测试函数,在这个函数里可以使用任何C++代码并使用上面提供的断言来进行检查。

TEST的第一个参数是测试用例名,第二个参数是测试用例中某项测试的名称。一个测试用例可以包含任意数量的独立测试。这两个参数组成了一个测试的全称。

就前面的例子来说:

我们要测试这个函数:int Factorial(int n); // 返回n的阶乘

我们的测试用例是:1.测试输入0的情况; 2.测试输入其它数据的情况,于是就有了:

  1. TEST(TestFactorial, ZeroInput) {
  2.     EXPECT_EQ(1, Factorial(0));
  3. }
  4.  
  5. TEST(TestFactorial, OtherInput) {
  6.     EXPECT_EQ(1, Factorial(1));
  7.     EXPECT_EQ(2, Factorial(2));
  8.     EXPECT_EQ(6, Factorial(3));
  9.     EXPECT_EQ(40320, Factorial(8));

Google Test根据测试用例来分组收集测试结果。因此,逻辑相关的测试应该在同一测试用例中;换句话说,它们的TEST()的第一个参数应该是一样的。在上面的例子中,我们有两个测试,ZeroInputOtherInput,它们都属于同一个测试用例 TestFactorial

TEST_F宏:

TEST_F宏用于在多个测试中使用同样的数据配置,所以它又叫:测试夹具(Test Fixtures)

如果我们的多个测试要使用相同的数据(如前例中,我们的Test_GFNTest_GP都使用程序自身的完整文件名来测试),就可以采用一个测试夹具。

要创建测试夹具,只需:

  1. 创建一个类继承自testing::Test。将其中的成员声明为protected:或是public:,因为我们想要从子类中存取夹具成员。
  2. 在该类中声明测试中所要使用到的数据。
  3. 如果需要,编写一个默认构造函数或者SetUp()函数来为每个测试准备对象。
  4. 如果需要,编写一个析构函数或者TearDown()函数来释放你在SetUp()函数中申请的资源。
  5. 如果需要,定义你的测试所需要共享的子程序。

当我们要使用夹具时,使用TEST_F()替换掉TEST(),它允许我们存取测试固件中的对象和子程序:

  1. TEST_F(test_case_name, test_name) {
  2. ... test body ...

与TEST()一样,第一个参数是测试用例的名称,但对TEST_F()来说,这个名称必须与测试夹具类的名称一样。

对于TEST_F()中定义的每个测试,Google Test将会:

  1. 创建一个全新的测试夹具
  2. 通过SetUp()初始化它,
  3. 运行测试
  4. 调用TearDown()来进行清理工作
  5. 删除测试夹具。

注意,同一测试用例中,不同的测试拥有不同的测试夹具。Google Test不会对多个测试重用一个测试夹具,测试对测试夹具的改动并不会影响到其他测试。

调用测试

TEST()TEST_F()向Google Test隐式注册它们的测试。因此,与很多其他的C++测试框架不同,你不需要为了运行你定义的测试而将它们全部再列出来一次。

在定义好测试后,你可以通过RUN_ALL_TESTS()来运行它们,如果所有测试成功,该函数返回0,否则会返回1.注意RUN_ALL_TESTS()会运行你链接到的所有测试——它们可以来自不同的测试用例,甚至是来自不同的文件。

当被调用时,RUN_ALL_TESTS()宏会:

  1. 保存所有的Google Test标志。
  2. 为一个测试创建测试夹具对象。
  3. 调用SetUp()初始化它。
  4. 在固件对象上运行测试。
  5. 调用TearDown()清理夹具。
  6. 删除固件。
  7. 恢复所有Google Test标志的状态。
  8. 重复上诉步骤,直到所有测试完成。

此外,如果第二步时,测试夹具的构造函数产生一个致命错误,继续执行3至5部显然没有必要,所以它们会被跳过。与之相似,如果第3部产生致命错误,第4部也会被跳过。

重要:你不能忽略掉RUN_ALL_TESTS()的返回值,否则gcc会报一个编译错误。这样设计的理由是自动化测试服务会根据测试退出返回码来决定一个测试是否通过,而不是根据其stdout/stderr输出;因此你的main()函数必须返回RUN_ALL_TESTS()的值。

而且,你应该只调用RUN_ALL_TESTS()一次。多次调用该函数会与Google Test的一些高阶特性(如线程安全死亡测试thread-safe death tests)冲突,因而是不被支持的。

编写main()函数

你可以从下面这个模板开始:

  1. #include "this/package/foo.h"
  2. #include <gtest/gtest.h>
  3. namespace {
  4.     // 用于测试Foo类的测试夹具
  5.     class FooTest : public testing::Test {
  6.     protected:
  7.         // 下面四个方法如果没有代码的话的可以不用定义
  8.         FooTest() {
  9.             // 这里可以为每个测试做一些设置工作
  10.         }
  11.         virtual ~FooTest() {
  12.             // 可以做一些不会抛出异常的清理工作
  13.         }
  14.         // 如果构造和析构还不能满足你的设置、清理工作
  15.         // 你还可以在下面两个函数里做这些
  16.         virtual void SetUp() {
  17.             // 这里的代码在构造之后(在每次测试之前)马上执行
  18.         }
  19.         virtual void TearDown() {
  20.             // 这里的代码在每次测试之后(在析构之前)马上执行
  21.         }
  22.         // 可以在这里定义所有要用到 Foo 的测试用例中所需要的对象.
  23.     };
  24.     // 测试做Abc操作的Foo::Bar() 方法.
  25.     TEST_F(FooTest, MethodBarDoesAbc) {
  26.         const string input_filepath = "myinputfile.dat";
  27.         const string output_filepath = "myoutputfile.dat";
  28.         Foo f;
  29.         EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
  30.     }
  31.     // 测试 Foo 的 Xyz 操作.
  32.     TEST_F(FooTest, DoesXyz) {
  33.         // Exercises the Xyz feature of Foo.
  34.     }
  35. }  // namespace
  36. int main(int argc, char **argv) {
  37.     testing::InitGoogleTest(&argc, argv);
  38.     return RUN_ALL_TESTS();

testing::InitGoogleTest() 函数负责解析命令行传入的Google Test标志,并删除所有它可以处理的标志。这使得用户可以通过各种不同的标志控制一个测试程序的行为。关于这一点我们会在GTestAdvanced中讲到。你必须在调用RUN_ALL_TESTS()之前调用该函数,否则就无法正确地初始化标示。

在Windows上InitGoogleTest()可以支持宽字符串,所以它也可以被用在以UNICODE模式编译的程序中。

附:BCB6 Port和 Mingw Port

Google C++ Testing Framework for BCB6

Google C++ Testing Framework for Mingw

 

Google Test Primer :开始使用Google C++ 测试框架

Google C++ Testing Framework Primer

 

翻译:Ray Li (ray.leex@gmail.com)
修改日期:2008年7月6日
原文参见:http://code.google.com/p/googletest/wiki/GoogleTestPrimer

 

Introduction:为什么需要Google C++ 测试框架?

 

Google C++ 测试框架帮助你更好地编写C++测试。

 

无论你是在Linux,Windows,还是Mac环境下工作,只要你编写C++代码,Google 测试框架都可以帮上忙。

 

那么,哪些因素才能构成一个好的测试?以及,Google C++ 测试框架怎样满足这些因素?我们相信:

  1. 测试应该是独立可重复的。因为其他测试成功或失败而导致我们要对自己的测试进行debug是非常痛苦的。Google C++ 测试框架通过将每个测试在不同的对象中运行,使得测试分离开来。当一个测试失败时,Google C++ 测试框架允许你独立运行它以进行快速除错。
  2. 测试应该能够被很好地组织,并反映被测代码的结构。Google C++ 测试框架将测试组织成测试案例,案例中的测试可以共享数据和程序分支。这样一种通用模式能够很容易辨识,使得我们的测试容易维护。当开发人员在项目之间转换,开始在一个新的代码基上开始工作时,这种一致性格外有用。
  3. 测试应该是可移植可重用的。开源社区有很多平台独立的代码,它们的测试也应该是平台独立的。除开一些特殊情况,Google C++ 测试框架运行在不同的操作系统上、与不同的编译器(gcc、icc、MSVC)搭配,Google C++ 测试框架的测试很容易与不同的配置一起工作。
  4. 当测试失败时,应该提供尽可能多的、关于问题的信息。Google C++ 测试框架在第一个测试失败时不会停下来。相反,它只是将当前测试停止,然后继续接下来的测试。你也可以设置对一些非致命的错误进行报告,并接着进行当前的测试。这样,你就可以在一次“运行-编辑-编译”循环中检查到并修复多个bug。
  5. 测试框架应该能将测试编写人员从一些环境维护的工作中解放出来,使他们能够集中精力于测试的内容。Google C++ 测试框架自动记录下所有定义好的测试,不需要用户通过列举来指明哪些测试需要运行。
  6. 测试应该快速。使用Google C++ 测试框架,你可以重用多个测试的共享资源,一次性完成设置/解除设置,而不用使一个测试去依赖另一测试。

因为Google C++ 测试框架基于著名的xUnit架构,如果你之前使用过JUnit或PyUnit的话,你将会感觉非常熟悉。如果你没有接触过这些测试框架,它也只会占用你大约10分钟的时间来学习基本概念和上手。所以,让我们开始吧!

 

Note:本文偶尔会用“Google Test”来代指“Google C++ 测试框架”。

 

基本概念

 

使用Google Test时,你是从编写断言开始的,而断言是一些检查条件是否为真的语句。一个断言的结果可能是成功、非致命失败,或者致命失败。如果一个致命失败出现,他会结束当前的函数;否则,程序继续正常运行。

 

测试使用断言来验证被测代码的行为。如果一个测试崩溃或是出现一个失败的断言,那么,该测试失败;否则该测试成功

 

一个测试案例(test case)包含了一个或多个测试。你应该将自己的测试分别归类到测试案例中,以反映被测代码的结构。当测试案例中的多个测试需要共享通用对象和子程序时,你可以把他们放到一个测试固件(test fixture)类中。

 

一个测试程序可以包含多个测试案例。

 

从编写单个的断言开始,到创建测试和测试案例,我们将会介绍怎样编写一个测试程序。

 

断言

 

Google Test中的断言是一些与函数调用相似的宏。要测试一个类或函数,我们需要对其行为做出断言。当一个断言失败时,Google Test会在屏幕上输出该代码所在的源文件及其所在的位置行号,以及错误信息。也可以在编写断言时,提供一个自定义的错误信息,这个信息在失败时会被附加在Google Test的错误信息之后。

 

断言常常成对出现,它们都测试同一个类或者函数,但对当前功能有着不同的效果。ASSERT_*版本的断言失败时会产生致命失败,并结束当前函数。EXPECT_*版本的断言产生非致命失败,而不会中止当前函数。通常更推荐使用EXPECT_*断言,因为它们运行一个测试中可以有不止一个的错误被报告出来。但如果在编写断言如果失败,就没有必要继续往下执行的测试时,你应该使用ASSERT_*断言。

 

因为失败的ASSERT_*断言会立刻从当前的函数返回,可能会跳过其后的一些的清洁代码,这样也许会导致空间泄漏。根据泄漏本身的特质,这种情况也许值得修复,也可能不值得我们关心——所以,如果你得到断言错误的同时,还得到了一个堆检查的错误,记住上面我们所说的这一点。

 

要提供一个自定义的错误消息,只需要使用<<操作符,或一个<<操作符的序列,将其输入到框架定义的宏中。下面是一个例子:

 

 
Cpp代码 复制代码
  1. ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";   
  2. for (int i = 0; i < x.size(); ++i) {   
  3.   EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;   
  4. }  
 

任何能够被输出到ostream中的信息都可以被输出到一个断言宏中——特别是C字符串和string对象。如果一个宽字符串(wchar_t*,windows上UNICODE模式TCHAR*或std::wstring)被输出到一个断言中,在打印时它会被转换成UTF-8编码。

 

基本断言

 

下面这些断言实现了基本的true/false条件测试。

 

致命断言 非致命断言 验证条件
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition为真
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition 为假

 

记住,当它们失败时,ASSERT_*产生一个致命失败并从当前函数返回,而EXCEPT_*产生一个非致命失败,允许函数继续运行。在两种情况下,一个断言失败都意味着它所包含的测试失败。

 

有效平台:Linux、Windows、Mac。

 

二进制比较

 

本节描述了比较两个值的一些断言。

 

致命断言 非致命断言 验证条件
ASSERT_EQ(expected, actual); EXPECT_EQ(expected, actual); expected == actual
ASSERT_NE(val1, val2); EXPECT_NE(val1, val2); val1 != val2
ASSERT_LT(val1, val2); EXPECT_LT(val1, val2); val1 < val2
ASSERT_LE(val1, val2); EXPECT_LE(val1, val2); val1 <= val2
ASSERT_GT(val1, val2); EXPECT_GT(val1, val2); val1 > val2
ASSERT_GE(val1, val2); EXPECT_GE(val1, val2); val1 >= val2

 

在出现失败事件时,Google Test会将两个值(Val1Val2)都打印出来。在ASSERT_EQ*和EXCEPT_EQ*断言(以及我们随后介绍类似的断言)中,你应该把你希望测试的表达式放在actual(实际值)的位置上,将其期望值放在expected(期望值)的位置上,因为Google Test的测试消息为这种惯例做了一些优化。

 

参数值必须是可通过断言的比较操作符进行比较的,否则你会得到一个编译错误。参数值还必须支持<<操作符来将值输入到ostream中。所有的C++内置类型都支持这一点。

 

这些断言可以用于用户自定义的型别,但你必须重载相应的比较操作符(如==、<等)。如果定义有相应的操作符,推荐使用ASSERT_*()宏,因为它们不仅会输出比较的结果,还会输出两个比较对象。

 

参数表达式总是只被解析一次。因此,参数表达式有一定的副作用(side effect,这里应该是指编译器不同,操作符解析顺序的不确定性)也是可以接受的。但是,同其他普通C/C++函数一样,参数表达式的解析顺序是不确定的(如,一种编译器可以自由选择一种顺序来进行解析),而你的代码不应该依赖于某种特定的参数解析顺序。

 

ASSERT_EQ()对指针进行的是指针比较。即,如果被用在两个C字符串上,它会比较它们是否指向同样的内存地址,而不是它们所指向的字符串是否有相同值。所以,如果你想对两个C字符串(例如,const char*)进行值比较,请使用ASSERT_STREQ()宏,该宏会在后面介绍到。特别需要一提的是,要验证一个C字符串是否为空(NULL),使用ASSERT_STREQ(NULL, c_string)。但是要比较两个string对象时,你应该使用ASSERT_EQ。

 

本节中介绍的宏都可以处理窄字符串对象和宽字符串对象(string和wstring)。

 

有效平台:Linux、Windows、Mac。

 

字符串比较

 

该组断言用于比较两个C字符串。如果你想要比较两个string对象,相应地使用EXPECT_EQ、EXPECT_NE等断言。

 

致命断言 非致命断言 验证条件
ASSERT_STREQ(expected_str, actual_str); EXPECT_STREQ(expected_str, actual_str); 两个C字符串有相同的内容
ASSERT_STRNE(str1, str2); EXPECT_STRNE(str1, str2); 两个C字符串有不同的内容
ASSERT_STRCASEEQ(expected_str, actual_str); EXPECT_STRCASEEQ(expected_str, actual_str); 两个C字符串有相同的内容,忽略大小写
ASSERT_STRCASENE(str1, str2); EXPECT_STRCASENE(str1, str2); 两个C字符串有不同的内容,忽略大小写

 

注意断言名称中出现的“CASE”意味着大小写被忽略了。

 

*STREQ*和*STRNE*也接受宽字符串(wchar_t*)。如果两个宽字符串比较失败,它们的值会做为UTF-8窄字符串被输出。

 

一个NULL空指针和一个空字符串会被认为是不一样的。

 

有效平台:Linux、Windows、Mac。

 

参见:更多的字符串比较的技巧(如子字符串、前缀和正则表达式匹配),请参见[Advanced Guide Advanced Google Test Guide]。

 

简单的测试

 

要创建一个测试:

  1. 使用TEST()宏来定义和命名一个测试函数,它们是一些没有返回值的普通C++函数。
  2. 在这个函数中,与你想要包含的其它任何有效C++代码一起,使用Google Test提供的各种断言来进行检查。
  3. 测试的结果由其中的断言决定;如果测试中的任意断言失败(无论是致命还是非致命),或者测试崩溃,那么整个测试就失败了。否则,测试通过。 
Cpp代码 复制代码
  1. TEST(test_case_name, test_name) {   
  2. ... test body ...   
  3. }  
 

TEST()的参数是从概括到特殊的。第一个参数是测试案例的名称,第二个参数是测试案例中的测试的名称。记住,一个测试案例可以包含任意数量的独立测试。一个测试的全称包括了包含它的测试案例名称,及其独立的名称。不同测试案例中的独立测试可以有相同的名称。

 

举例来说,让我们看一个简单的整数函数:

 

Cpp代码 复制代码
  1. int Factorial(int n); // 返回n的阶乘  
 

 

这个函数的测试案例应该看起来像是:

 

Cpp代码 复制代码
  1. // 测试0的阶乘   
  2. TEST(FactorialTest, HandlesZeroInput) {   
  3.   EXPECT_EQ(1, Factorial(0));   
  4. }   
  5. // 测试正数的阶乘   
  6. TEST(FactorialTest, HandlesPositiveInput) {   
  7.   EXPECT_EQ(1, Factorial(1));   
  8.   EXPECT_EQ(2, Factorial(2));   
  9.   EXPECT_EQ(6, Factorial(3));   
  10.   EXPECT_EQ(40320, Factorial(8));   
  11. }  
 

 

Google Test根据测试案例来分组收集测试结果,因此,逻辑相关的测试应该在同一测试案例中;换句话说,它们的TEST()的第一个参数应该是一样的。在上面的例子中,我们有两个测试,HandlesZeroInput和HandlesPostiveInput,它们都属于同一个测试案例FactorialTest。

 

有效平台:Linux、Windows、Mac。

 

测试固件(Test Fixtures,又做测试夹具、测试套件):在多个测试中使用同样的数据配置

 

当你发现自己编写了两个或多个测试来操作同样的数据,你可以采用一个测试固件。它让你可以在多个不同的测试中重用同样的对象配置。

 

要创建测试固件,只需:

  1. 创建一个类继承自testing::Test。将其中的成员声明为protected:或是public:,因为我们想要从子类中存取固件成员。
  2. 在该类中声明你计划使用的任何对象。
  3. 如果需要,编写一个默认构造函数或者SetUp()函数来为每个测试准备对象。常见错误包括将SetUp()拼写为Setup()(小写了u)——不要让它发生在你身上。
  4. 如果需要,编写一个析构函数或者TearDown()函数来释放你在SetUp()函数中申请的资源。要知道什么时候应该使用构造函数/析构函数,什么时候又应该使用SetUp()/TearDown()函数,阅读我们的FAQ。
  5. 如果需要,定义你的测试所需要共享的子程序。

当我们要使用固件时,使用TEST_F()替换掉TEST(),它允许我们存取测试固件中的对象和子程序:

 

Cpp代码 复制代码
  1. TEST_F(test_case_name, test_name) {   
  2. ... test body ...   
  3. }  

 

与TEST()一样,第一个参数是测试案例的名称,但对TEST_F()来说,这个名称必须与测试固件类的名称一些。你可能已经猜到了:_F正是指固件。

 

不幸地是,C++宏系统并不允许我们创建一个单独的宏来处理两种类型的测试。使用错误的宏会导致编译期的错误。

 

而且,你必须在TEST_F()中使用它之前,定义好这个测试固件类。否则,你会得到编译器的报错:“virtual outside class declaration”。

对于TEST_F()中定义的每个测试,Google Test将会:

  1. 在运行时创建一个全新的测试固件
  2. 马上通过SetUp()初始化它,
  3. 运行测试
  4. 调用TearDown()来进行清理工作
  5. 删除测试固件。注意,同一测试案例中,不同的测试拥有不同的测试固件。Google Test在创建下一个测试固件前总是会对现有固件进行删除。Google Test不会对多个测试重用一个测试固件。测试对测试固件的改动并不会影响到其他测试。

例如,让我们为一个名为Queue的FIFO队列类编写测试,该类的接口如下:

 
Cpp代码 复制代码
  1. template <typename E> // E为元素类型   
  2. class Queue {   
  3. public:   
  4.   Queue();   
  5.   void Enqueue(const E& element);   
  6.   E* Dequeue(); // 返回 NULL 如果队列为空.   
  7.   size_t size() const;   
  8.   ...   
  9. };  

 

首先,定义一个固件类。习惯上,你应该把它的名字定义为FooTest,这里的Foo是被测试的类。

 
Cpp代码 复制代码
  1. class QueueTest : public testing::Test {   
  2. protected:   
  3.   virtual void SetUp() {   
  4.     q1_.Enqueue(1);   
  5.     q2_.Enqueue(2);   
  6.     q2_.Enqueue(3);   
  7.   }   
  8.   // virtual void TearDown() {}   
  9.   Queue<int> q0_;   
  10.   Queue<int> q1_;   
  11.   Queue<int> q2_;   
  12. };  
 

在这个案例中,我们不需要TearDown(),因为每个测试后除了析构函数外不需要进行其它的清理工作了。

 

接下来我们使用TEST_F()和这个固件来编写测试。

 

Cpp代码 复制代码
  1. TEST_F(QueueTest, IsEmptyInitially) {   
  2.   EXPECT_EQ(0, q0_.size());   
  3. }   
  4. TEST_F(QueueTest, DequeueWorks) {   
  5.   int* n = q0_.Dequeue();   
  6.   EXPECT_EQ(NULL, n);   
  7.   
  8.   n = q1_.Dequeue();   
  9.   ASSERT_TRUE(n != NULL);   
  10.   EXPECT_EQ(1, *n);   
  11.   EXPECT_EQ(0, q1_.size());   
  12.   delete n;   
  13.   
  14.   n = q2_.Dequeue();   
  15.   ASSERT_TRUE(n != NULL);   
  16.   EXPECT_EQ(2, *n);   
  17.   EXPECT_EQ(1, q2_.size());   
  18.   delete n;   
  19. }  
 

上面这段代码既使用了ASSERT_*断言,又使用了EXPECT_*断言。经验上讲,如果你想要断言失败后,测试能够继续进行以显示更多的错误时,你应该使用EXPECT_*断言;使用ASSERT_*如果该断言失败后继续往下执行毫无意义。例如,Dequeue测试中的第二个断言是ASSERT_TURE(n!= NULL),因为我们随后会n指针解引用,如果n指针为空的话,会导致一个段错误。

 

当这些测试开始时,会发生如下情况:

  1. Google Test创建一个QueueTest对象(我们把它叫做t1)。
  2. t1.SetUp()初始化t1。
  3. 第一个测试(IsEmptyInitiallly)在t1上运行。
  4. 测试完成后,t1.TearDown()进行一些清理工作。
  5. t1被析构。
  6. 以上步骤在另一个QueueTest对象上重复进行,这回会运行DequeueWorks测试。

有效平台:Linux、Windows、Mac。

 

注意:当一个测试对象被构造时,Google Test会自动地保存所有的Google Test变量标识,对象析构后进行恢复。

 

调用测试

 

TEST()和TEST_F()向Google Test隐式注册它们的测试。因此,与很多其他的C++测试框架不同,你不需要为了运行你定义的测试而将它们全部再列出来一次。

 

在定义好测试后,你可以通过RUN_ALL_TESTS()来运行它们,如果所有测试成功,该函数返回0,否则会返回1.注意RUN_ALL_TESTS()会运行你链接到的所有测试——它们可以来自不同的测试案例,甚至是来自不同的文件。

 

当被调用时,RUN_ALL_TESTS()宏会:

  1. 保存所有的Google Test标志。
  2. 为一个侧测试创建测试固件对象。
  3. 调用SetUp()初始化它。
  4. 在固件对象上运行测试。
  5. 调用TearDown()清理固件。
  6. 删除固件。
  7. 恢复所有Google Test标志的状态。
  8. 重复上诉步骤,直到所有测试完成。

此外,如果第二步时,测试固件的构造函数产生一个致命错误,继续执行3至5部显然没有必要,所以它们会被跳过。与之相似,如果第3部产生致命错误,第4部也会被跳过。

 

重要:你不能忽略掉RUN_ALL_TESTS()的返回值,否则gcc会报一个编译错误。这样设计的理由是自动化测试服务会根据测试退出返回码来决定一个测试是否通过,而不是根据其stdout/stderr输出;因此你的main()函数必须返回RUN_ALL_TESTS()的值。

 

而且,你应该只调用RUN_ALL_TESTS()一次。多次调用该函数会与Google Test的一些高阶特性(如线程安全死亡测试thread-safe death tests)冲突,因而是不被支持的。

 

有效平台:Linux、Windows、Mac。

 

编写main()函数

 

你可以从下面这个样板开始:

 

Cpp代码 复制代码
  1. #include "this/package/foo.h"   
  2. #include <gtest/gtest.h>   
  3. namespace {   
  4. // 测试Foo类的测试固件   
  5. class FooTest : public testing::Test {   
  6. protected:   
  7.   // You can remove any or all of the following functions if its body   
  8.   // is empty.   
  9.   FooTest() {   
  10.     // You can do set-up work for each test here.   
  11.   }   
  12.   virtual ~FooTest() {   
  13.     // You can do clean-up work that doesn't throw exceptions here.   
  14.   }   
  15.   // If the constructor and destructor are not enough for setting up   
  16.   // and cleaning up each test, you can define the following methods:   
  17.   virtual void SetUp() {   
  18.     // Code here will be called immediately after the constructor (right   
  19.     // before each test).   
  20.   }   
  21.   virtual void TearDown() {   
  22.     // Code here will be called immediately after each test (right   
  23.     // before the destructor).   
  24.   }   
  25.   // Objects declared here can be used by all tests in the test case for Foo.   
  26. };   
  27.   
  28. // Tests that the Foo::Bar() method does Abc.   
  29. TEST_F(FooTest, MethodBarDoesAbc) {   
  30.   const string input_filepath = "this/package/testdata/myinputfile.dat";   
  31.   const string output_filepath = "this/package/testdata/myoutputfile.dat";   
  32.   Foo f;   
  33.   EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));   
  34. }   
  35.   
  36. // Tests that Foo does Xyz.   
  37. TEST_F(FooTest, DoesXyz) {   
  38.   // Exercises the Xyz feature of Foo.   
  39. }   
  40. }  // namespace   
  41.   
  42. int main(int argc, char **argv) {   
  43.   testing::InitGoogleTest(&argc, argv);   
  44.   return RUN_ALL_TESTS();   
  45. }  

 

testing::InitGoogleTest()函数负责解析命令行传入的Google Test标志,并删除所有它可以处理的标志。这使得用户可以通过各种不同的标志控制一个测试程序的行为。关于这一点我们会在GTestAdvanced中讲到。你必须在调用RUN_ALL_TESTS()之前调用该函数,否则就无法正确地初始化标示。

 

在Windows上InitGoogleTest()可以支持宽字符串,所以它也可以被用在以UNICODE模式编译的程序中。

 

进阶阅读

 

恭喜你!你已经学到了一些Google Test基础。你可以从编写和运行几个Google Test测试开始,再阅读一下GoogleTestSamples,或是继续研究GoogleTestAdvancedGuide,其中描述了很多更有用的Google Test特性。

 

已知局限

 

Google Test被设计为线程安全的。但是,我们还没有时间在各种平台上实现同步原语(synchronization primitives)。因此,目前从两个线程同时使用Google Test断言是不安全的。由于通常断言是在主线程中完成的,因此在大多数测试中这都不算问题。如果你愿意帮忙,你可以试着在gtest-port.h中实现必要的同步原语。