使用Google C++ Testing Framework进行单元测试
前几个月Google开源了它的测试框架,自称其旗下的上千个项目都在使用它。今天我们就用它来尝尝鲜吧?:-)
安装:
下载Google C++ Testing Framework,解压...
VC2005:
直接打开msvc\gtest.vcproj或msvc\gtest.sln,直接编译即可。
Linux/Unix下的GCC:
传统过程:./configure make
Mingw:
BCC:
用Mingw和BCB6编译需要修改一些代码,过几天我会上传到www.cppprog.com网站上。
使用:
首先#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. //用TEST做简单测试
7. TEST(TestFactorial, ZeroInput) //第一个参数是测试用例名,第二个参数是测试名:随后的测试结果将以"测试用例名.测试名"的形式给出
8. {
9. EXPECT_EQ(1, Factorial(0)); //EXPECT_EQ稍候再说,现在只要知道它是测试两个数据是否相等的就行了。
10. }
11. TEST(TestFactorial, OtherInput)
12. {
13. EXPECT_EQ(1, Factorial(1));
14. EXPECT_EQ(2, Factorial(2));
15. EXPECT_EQ(6, Factorial(3));
16. EXPECT_EQ(40320, Factorial(8));
17. }
18. int main(int argc, TCHAR* argv[])
19. {
20. testing::InitGoogleTest(&argc,argv); //用来处理Test相关的命令行开关,如果不关注也可不加
21. RUN_ALL_TESTS(); //看函数名就知道干啥了
22. std::cin.get(); //只是让它暂停而已,不然一闪就没了
23. return 0;
24. }
25. //---------------------------------------------------------------------------
运行结果:
瞧:测试框架指出: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. virtual void SetUp() {
11. // 在构造后调用
12. strExe.resize(MAX_PATH);
13. GetModuleFileName(NULL, &strExe[0], MAX_PATH);
14. }
15. virtual void TearDown() { } // 在析构前调用
16. };
17. tstring getfilename(const tstring &full) //偶写的从完整路径里取出文件名的函数(路径分隔符假定为'\\')
18. {
19. return full.substr(full.rfind(_T('\\')));
20. }
21. tstring getpath(const tstring &full) //偶写的从完整路径里取出路径名的函数(Windows路径)
22. {
23. return full.substr(0, full.rfind(_T('\\')));
24. }
25. TEST_F(FooTest, Test_GFN) //测试getfilename函数
26. {
27. EXPECT_STREQ(_T("Project1.exe"), getfilename(strExe).c_str());
28. }
29. TEST_F(FooTest, Test_GP) //测试getpath函数
30. {
31. EXPECT_STREQ(_T("D:\\Code\\libs\\google\\gtest- 1.2.1 \\BCC_SPC\\bcc\\ex"), getpath(strExe).c_str());
32. }
33. int main(int argc, TCHAR* argv[]) //主函数还是一样地
34. {
35. testing::InitGoogleTest(&argc,argv);
36. RUN_ALL_TESTS();
37. std::cin.get();
38. return 0;
39. }
运行结果:
瞧,Google C++ 测试框架毫不客气地指出偶的getfilename返回的字符串比预期的多了一个'\\'
快速入门:
Google提供了两种断言形式,一种以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的阶乘
我们的测试用例是:测试输入0的情况,测试输入其它数据的情况,于是就有了:
1. TEST(TestFactorial, ZeroInput) //第一个参数是测试用例名,第二个参数是测试名:随后的测试结果将以"测试用例名.测试名"的形式给出
2. {
3. EXPECT_EQ(1, Factorial(0)); //EXPECT_EQ稍候再说,现在只要知道它是测试两个数据是否相等的就行了。
4. }
5. TEST(TestFactorial, OtherInput)
6. {
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()的第一个参数应该是一样的。在上面的例子中,我们有两个测试,ZeroInput和OtherInput,它们都属于同一个测试用例TestFactorial。
TEST_F宏:
TEST_F宏用于在多个测试中使用同样的数据配置,所以它又叫:测试夹具(Test Fixtures)
如果我们的多个测试要使用相同的数据(如前例中,我们的Test_GFN和Test_GP都使用程序自身的完整文件名来测试),就可以采用一个测试夹具。
要创建测试固件,只需:
1. 创建一个类继承自testing::Test。将其中的成员声明为protected:或是public:,因为我们想要从子类中存取夹具成员。
2. 在该类中声明测试中所要使用到的数据。
3. 如果需要,编写一个默认构造函数或者SetUp()函数来为每个测试准备对象。
4. 如果需要,编写一个析构函数或者TearDown()函数来释放你在SetUp()函数中申请的资源。
5. 如果需要,定义你的测试所需要共享的子程序。
当我们要使用固件时,使用TEST_F()替换掉TEST(),它允许我们存取测试固件中的对象和子程序:
TEST_F(test_case_name, test_name) {
... 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. // 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. // Tests that the Foo::Bar() method does Abc.
28. TEST_F(FooTest, MethodBarDoesAbc) {
29. const string input_filepath = "this/package/testdata/myinputfile.dat";
30. const string output_filepath = "this/package/testdata/myoutputfile.dat";
31. Foo f;
32. EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
33. }
34. // Tests that Foo does Xyz.
35. TEST_F(FooTest, DoesXyz) {
36. // Exercises the Xyz feature of Foo.
37. }
38. } // namespace
39. int main(int argc, char **argv) {
40. testing::InitGoogleTest(&argc, argv);
41. return RUN_ALL_TESTS();
42. }
testing::InitGoogleTest() 函数负责解析命令行传入的Google Test标志,并删除所有它可以处理的标志。这使得用户可以通过各种不同的标志控制一个测试程序的行为。关于这一点我们会在GTestAdvanced中讲到。你必须在调用RUN_ALL_TESTS()之前调用该函数,否则就无法正确地初始化标示。
在Windows上InitGoogleTest()可以支持宽字符串,所以它也可以被用在以UNICODE模式编译的程序中。
Google test for mingw 下载: http://www.cppprog.com/2009/0101/26.html Google test for bcb 下载: http://www.cppprog.com/2009/0101/27.html
发表于 @ 2008年12月30日 19:46:00|评论(13 )|收藏
新一篇: Google C++ Mocking Framework使用简介 | 旧一篇: BCB与WTL(续-VCL和WTL混用实例)
lizhe1985 发表于 2008年 12月 31日 8:58:11 IP:举报
您的文章已经被推荐到CSDN首页专家专栏栏目,将被更多的CSDN网友阅读与分享。感谢您对CSDN博客的支持。
wslgz 发表于 2008年 12月 31日 15:37:49 IP:举报
不知道能不能给个vc的例子,有些地方不是很明白
wslgz 发表于 2008年 12月 31日 15:37:58 IP:举报
不知道能不能给个vc的例子,有些地方不是很明白
wslgz 发表于 2008年 12月 31日 15:38:31 IP:举报
不知道能不能给个vc的例子,有些地方不是很明白
cairuichu 发表于 2008年 12月 31日 17:10:52 IP:举报
同求vc的例子,最好6.0,^_^
cairuichu 发表于 2008年 12月 31日 17:10:56 IP:举报
同求vc的例子,最好6.0,^_^
cairuichu 发表于 2008年 12月 31日 17:11:01 IP:举报
同求vc的例子,最好6.0,^_^
Waiting4you 发表于 2009年 1月 1日 11:51:59 IP:举报
VC是最简单的,从官网上下载以后里面就自带了VC的工程文件(包含例子和库文件),直接打开编译就可以了。
注意库文件默认是“多线程”而不是“多线程DLL”。
我上面的代码就是在VC2005里修改的。
至于VC6.0,我没试过,不过可以考虑去下载我修改的for BCB6.0的代码,VC6.0应该也能编译通过。
olay105 发表于 2009年 1月 1日 18:29:28 IP:
总共15个,你知道怎么解决呢? 文章链接:http://blog.csdn.net/Waiting4you/archive/ 2008/12/30 /3652350.aspx 发表时间: 2009 年1 月1 日 18:29:28">举报
博主,我是第一次用vs2005,但是在实验你的文章中出现了很多问题,解决了一些,最后还是有一个棘手的,能帮我解决一下吗?可能是编译时的链接问题:error LNK2019: unresolved external symbol "protected: __thiscall testing::Test::Test(void)" (??0Test@testing@@IAE@XZ) referenced in function "public: __thiscall TestFactorial_ZeroInput_Test::TestFactorial_ZeroInput_Test(void)" (??0TestFactorial_ZeroInput_Test@@QAE@XZ) project1.obj
总共15个,你知道怎么解决呢?
Waiting4you 发表于 2009年 1月 2日 10:15:08 IP:举报
回olay105:
先打开msvc\gtest.sln,编译,生成Debug\gtestd.lib和Release\gtest.lib,这是库文件。
把这个gtest.lib或gtestd.lib加入到这个“出现很多问题”的工程里。注意,因为库文件工程里默认是“运行时库:多线程”,所以你的工程也要这么设置。
olay105 发表于 2009年 1月 3日 17:34:00 IP:举报
谢谢博主,我已经解决问题了,很开心!
koolhazz 发表于 2009年 1月 8日 15:35:00 IP:举报
楼主:
我直接使用VC2005编译的话也是有很多错误,
错误 13 未能删除文件“c:\code\googletest\msvc\Release\vc80.idb”。
请确保该文件未被其他进程打开并且未被写保护。 gtest_output_test_
不知道是什么原因呢?我的环境里面还安装了vc2003
谢谢。
koolhazz 发表于 2009年 1月 8日 15:35:12 IP:举报
楼主:
我直接使用VC2005编译的话也是有很多错误,
错误 13 未能删除文件“c:\code\googletest\msvc\Release\vc80.idb”。
请确保该文件未被其他进程打开并且未被写保护。 gtest_output_test_
不知道是什么原因呢?我的环境里面还安装了vc2003
谢谢。