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

Waiting4you 发布于 2009/05/05 22:55
阅读 1K+
收藏 2

安装:

下载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模 式编译的程序中。

加载中
0
itman_shiyong
itman_shiyong

如何用google c++ Testing Framework进行单元测试呢?

返回顶部
顶部