关于Google C++ Test框架

本文介绍了Google C++测试框架的特性,包括独立且可重复的测试、良好的组织结构、可移植性和可重用性。它支持Linux、Windows、Mac平台,强调测试的独立性、组织结构、可移植性、丰富的信息反馈以及简化测试人员的工作。文章详细阐述了框架如何工作,如通过Test Case和Test Suite组织测试,以及断言的使用。同时,提到了如何创建测试工程、使用Google Test宏以及编写测试用例和测试集的基本概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Google C++测试框架能帮助更好的进行C++的测试。

支持的平台:Linux,Windows,Mac

怎样算一个好的测试平台,为什么Google C++ Test会合适:

1. 测试应该是独立的、可重复的。如果一个测试的结果依赖于其它测试用例的结果,debug起来将会非常痛苦。Google C++测试框架在不同的对象中运行每一个测试,从而使测试集隔离开来。当一次测试失败,你可以独立执行这个测试用例来快速debugging。

2. 测试应该有好的组织结构,并且能很好的反应测试代码的结构。Google C++测试框架将相关测试分组到共享数据和子例程的测试集中。这种样式非常容易识别并且使测试易于维护。如果要切换工程,开始在新的代码库上工作,这种一致性非常有帮助。

3. 测试应该是可移植和可重用的。开源社区有很多平台无关的代码,所以测试也应该平台无关。Google C++测试框架可运行在不同的操作系统上,用不同的编译器,用(或不用)异常,因此Google C++测试框架能通过各种配置轻松完成。

4.当测试失败,应该提供尽可能多的相关信息。Google C++测试框架不会停止在第一个测试用例失败的地方。取而代之的是,仅停止当前的测试,继续下一个。你可以创建一个测试,用于报告非致命失败,这样,你能一次搞定多个bug。

5. 一个测试框架,应该让测试人员从繁琐的基础事务中解脱出来,去专注于测试内容。Google C++测试框架自动跟踪所有的测试定义,不需要使用者运行时一一进行枚举。

6. 测试需要关注效率。Google C++测试框架,可跨测试重用共享资源,只需付出执行一次set-up/tear-down的代价,而不需要进行互相依赖。

因为Google C++测试框架基于流行的xUnit架构,如果你使用过JUnit或PyUnit,你将会发现非常容易掌握。如果没有,将花费你10分钟来学习一些基础。

接下来,会用Google Test来代替Google C++测试框架。

注意命名规则

注意:请不要将一些术语,如Test,Test Case,Test Suite等弄混淆。

Google Test会使用Test Case表示相关测试组,但现在的一些公共组织包括ISTQB等,常常用Test Suite来替代。

Google Test中的Test,对应于其它组织(如ISTQB)的Test Case。对比图如下。

Google Test术语   ISTQB术语      意思

Test()                     Test Case     使用特定的输入,来执行一个特殊的程序,并确认其结果

Test Case               Test Suite     一个组件相关的多个测试用例

创建一个新的测试工程

使用google test创建一个新的测试工程,需要先编译google test成lib文件,并且让你的工程进行链接。我们对一些流行的编译系统提供了编译文件:msvc--用于visual studio,xcode--用于Max Xcode,make--用于GUN make,codegear--用于Borland C++编译器;根目录下CMakeLists.txt--用于CMake(推荐)。如果你的编译系统不在上述列表中,你可以参考make/Makefile,学习Google Test编译的方法(通常,编译src/gtest-all.cc 和include中的文件即可。)

当你编译好Google Test的库之后,你可以开始创建自己的测试工程并进行编译。确保你的工程包含了Google Test的库和头文件。

如果有问题,可参考Google test的测试用例是如何编译的。

基本概念

Google Test从写断言开始,断言是一个用于判断条件真假的语句。一个断言的结果可能是成功,非致命错误或致命错误。如果一个致命错误发生,会从当前函数中退出;否则程序会继续正常执行。

测试使用断言来确定测试代码的行为。如果一条用例崩溃或者断言失败,该用例失败,否则成功。

一个测试集包含一个或多个测试用例。你应该把你的测试用例分为不同的测试集,以与测试代码的结构一致。当一个测试集中的多个测试用例需要共享通用对象和子例程,你可以把他们放到一个test fixture类中。

一个测试程序可包含多个测试集。

接下来我们会告诉你怎样编写一个测试程序,以一个独立的断言作为开始,来构造一个测试用例和测试集。

断言

Google Test的断言由宏来实现,宏类似于函数调用。可使用断言来判断一个类或者函数的行为。当断言失败,Google test会打印断言的源文件和行地址,以及失败消息。如果你提供了自定义失败信息,也会被添加到Google Test的消息中。

测试同样的事情但不同的效果时,经常成对使用断言。ASSERT_*,执行失败时,生成致命错误,退出当前函数;EXPECT_*,生成非致命错误,且不会退出当前函数。通常EXPECT_*会更受欢迎一些,他允许一次测试中包含多个失败。但如果一次测试失败后,后面的内容不再有任何意思,应该使用ASSERT_*。

ASSERT_*立即从当前函数退出,可能会跳过后面的清理代码,造成内存泄漏。根据泄漏的性质,它可能也可能不值得修复--如果除了断言错误外还有堆检查错误这一点,请记住。

如果想提供自定义的失败信息,可简单的使用"<<"操作符,或者是一系列的该运算符。如:

ASSERT_EQ(x.size(),y.size()) << "Vectors x and y are of unequal length";

for(int i=0;i<x.size();++i){

EXPECT_EQ(x[i],y[i]) << "Vectors x and y differ at index" << i;

}

所用可输出到输出流的内容,都能被输出到断言的宏中--尤其是C字符串和字符串对象。如果是宽字符串(wchar_t*,TCHAR* in UNICODE mode on windows,or std::wstring),会被打印为UTF-8。

基础断言

基出的true/false条件测试

致命断言                                          非致命断言                          证实内容

ASSERT_TRUE(condition)            EXPECT_TRUE(condition)        condition is true

ASSERT_FALSE(condition)            EXPECT_FALSE(condition)        condition is false

断言失败代表它包含失败的用例。

二进制比对

用于对比两个值

致命断言                                          非致命断言                            验证内容

ASSERT_EQ(val1,val2)              EXPECT_EQ(val1,val2)               val1 == val2

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_GT(val1,val2)               val1 >= val2

当执行失败时,Google Test会一并打印出val1和val2。

value参数的类型必须assertion对比表达式支持,负责会编译错误。

字符串比对

简单测试用例

为创建一个测试:

1. 使用TEST()宏定义一个测试函数,他们是普通的C++函数,不会返回值;

2.在该函数中,添加任何你想包含的C++语句,使用不同的Google Test断言来判断其值;

3. 测试结果将由断言决定;又任何断言失败或程序崩溃,整个测试失败,否则,成功。

TEST(testCaseName,testName){
...test body...
}

TEST()参数从一般到特殊。第一个参数是测试集的名字,第二个参数是测试用例的名字。名字必须是合法的C++标识符,且不能包含下划线。一个测试用例的全名由测试集和测试用例的名字构成。不同的测试集中可以有相同的测试用例名字。

例如,以一个简单的整数函数为例:

int  Factorial(int n);     //Returns the factorial of n

其测试集如下:

//Tests factorial of 0.
TEST(FactorialTest,HandlesZeroInput)
{
 EXPECT_EQ(1,Factorial(0));
}

//Test factorial of positive numbers
TEST(FactorialTest,HandlesPositiveInput)
{
EXPECT_EQ(1,Factorial(1));
EXPECT_EQ(2,Factorial(2));
EXPECT_EQ(6,Factorial(3));
EXPECT_EQ(40320,Factorial(8));
}

Google Test会根据测试集来返回测试结果,所以逻辑相关的测试用例最好放在一个测试集中;也就是说,Test()的第一个参数应该相同。在上面的例子中,两个测试用例属于相同的测试集。

Test Fixtures:多个测试用例使用相同的数据配置

如果你发现你写多条测试用例,操作的是相同的数据,你可以使用test fixture。它允许几个不同的测试用例重用对象的相同配置。

创建一个fixture,仅仅需要:

1. 从::testing::Test中派生出一个类。其body用protected:或public:开头,以便子类想要访问fixture的成员。

2.在类的类部,定义你打算使用的任何对象。

3. 如果可以,写一个默认的构建函数SetUp(),来为每一个测试用例创建对象。通常大家容易犯拼写错误,写成Setup().

4. 如果可以,写一个析构函数(),释放在SetUp()中使用的所有资源。

5.如果有需要,为你的测试用例创建可共享的子程序。

使用fixture时,我们使用TEST_F(),它允许你访问test fixture中所有的对象和子程序。

TEST_F(test_case_name,test_name)
{
...test body...
}

对于TEST_F(),第一个名字为test fixture类名。_F代表for fixture。

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

此外,你必须定义一个test fixture的类在使用TEST_F()之前,否则会编译错误“virtual outside class declaration”。

对TEST_F()定义的每一个测试用例,Google Test将:

1. 运行时创建一个新的测试夹具(test fixture)

2. 通过SetUp()立即初始化它;

3.运行测试;

4.调用TearDown清理资源

5.删除test fixture。注意在同一个测试集中,不同的测试用例有不同的test fixture对象,Google Test总是在创建下一个测试夹具前删除上一个测试夹具。Google Test不会对多个测试用例重用相同的测试夹具。一个测试用例的任何变化不会影响到其它的测试用例。

如下,使用FIFO 队列类Queue作为示例,该类接口如下:

template <typename E> // E is the element type.
class Queue {
 public:
  Queue();
  void Enqueue(const E& element);
  E* Dequeue(); // Returns NULL if the queue is empty.
  size_t size() const;
  ...
};

首先,定义一个夹具类。依照惯例,你应该给它一个名字FooTest(Foo代表被测试的类名)。

class QueueTest:public::testing::Test{
protected:
virtual void SetUp()
{
q0_.Enqueue(1);
q1_.Enqueue(2);
q2_.Enqueue(3);
}

//virtual void TestDown{}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};

在这个例子中,TearDown()不是必须的,我们不需要测试后再去清理资源空间,在析构器中已经完成了清理动作。

接下来编写TEST_F()和这个夹具。

TETS_F(QueueTest,IsEmptyInitially)
{
EXPECT_EQ(0,q0_.size());
}

TEST_F(QueueTest,DequeueWorks)
{
int *n = q0_.Dequeue();
EXPECT_EQ(NULL,n);

n=q1_.Dequeue();
ASSERT_TRUE(n!=NULL);
EXPECT_EQ(1,*n);
EXPECT_EQ(0,q1_.size());
delete n;

n = q2_.Dequeue();
ASSERT_TRUE(n!=NULL);
EXPECT_EQ(2,*n);
EXPECT_EQ(1,q2_.size());
delete n;
}

上面使用到了两种断言。

当测试程序跑起来,将会如下执行:

1. Google Test构建一个QueueTest对象(假设为t1).

2.t1.SetUp()初始化t1;

3.IsEmptyInitially在t1的环境下运行;

4.测试结束后t1.TearDown()清理环境;

5.析构t1.

6.在其它的QueueTest对象中重复上述操作,这一次运行的DequeueWorks测试用例。

注意:Google Test当一个测试对象被创建时,会自动存储所有的测试标记,对象析构时,自动恢复。

激活测试

在Google Test中,TEST()和TEST_F()隐式注册他们的测试用例。不像其他的许多C++测试框架,你需要列举所有定义过的测试用例来运行他们。

定义测试用例后,可通过RUN_ALL_TESTS()来执行,如果所有的测试用例执行成功,返回0,否则返回1. 注意:RUN_ALL_TESTS()运行所有的测试用例--这些测试用例可能来自不同的测试集,甚至是不同的源文件。

详解RUN_ALL_TESTS()宏:

1. 保存Google Test所有的状态;

2. 为第一次测试创建一个测试夹具对象;

3. 通过SetUp()初始化夹具对象;

4. 在夹具对象上运行测试用例;

5. 通过TeatDown清理夹具资源;

6. 删除夹具对象;

7. 恢复Google Test所有的状态;

8. 为下一条测试用例重复上述步骤,直至所有的测试用例执行完毕。

另外,如果在步骤2测试夹具的构造函数产生一个致命错误,step3~5将会跳过。同样,如果3产生致命错误,步骤4页会被跳过。

重点:main()必须以RUN_ALL_TESTS()作为返回值,否则gcc会报编译错误。返回值决定了相关测试是否通过,而不能仅关注stdout/stderr的内容。

而且RUN_ALL_TESTS()仅能调用一次。

编写main()函数:

可以下为模板:

#include “this/package/foo.h”
#include "gtest/gtest.h"
namespace{
//The fixture for testing class Foo.
class FooTest:public::testing::Test{
protected:
//You can remove any or all of the following functions if its body is empty
FooTest(){//You can do set-up work for each test here
}
virtual ~FooTest(){
//You can do clean-up work that doesn't throw exceptions here.
}
//如果构造函数和析构函数不能完成每次测试所有的创建和清理工作,你可以定义:
virtual void SetUp(){
//此处的代码会在每次测试前,构造函数创建后立即调用
}
virtual void TearDown(){
//此处的代码会在测试后,在析构函数前被调用
}
//此处声明的对象可被Foo的所有测试用例进行使用
};

//测试Foo::Bar()方法 does Abc:
TEST_F(FooTest,MethodBarDoesAbc){
const string input_filepath = "this/package/testdata/myinputfile.dat";
const string output_filepath = "this/package/testdata/myoutputfile.dat";
Foo f;
EXPECT_EQ(0,f.Bar(input_filepath,output_filepath));
}

//测试Foo does Xyz
TEST_F(FooTest,DoesXyz)
{//操作Foo的Xyz特性
}
}//namespace

int main(int argc,char **argv){
::testing::InitGoogleTest(&argc,argv);
return RUN_ALL_TESTS();
}

::testing::InitGoogleTest()函数为Google Test的标识进行命令行解析,并移除所有已识别的标志。这样用户可通过不同的标记来控制测试程序的不同行为,在AdvancedGuide中会有更多的说明。必须在RUN_ALL_TESTS()函数前调用该函数,否则标识不能正确初始化。

在windows中,InitGoogleTest()支持宽字符串,所以支持UNICODE编码模式。

或许你认为编写main()函数包含了太多的工作?我们完全同意你的看法,所以Google Test提供了基础的main() 的实现。如果它能满足你的需求,将你的程序与gtest_main()库进行链接即可。

Visual C++用户的重要提示

如果你把测试程序和main()函数封装在不同的库中,测试将不会被执行。这是Visual C++的一个bug。当定义你的测试用例后,Google Test创建特定的静态对象来注册测试用例。这些对象不需要被别处引用,构造函数仍认为它们在运行。当Visual C++链接器发现未被引用时,就不会去进行链接。如果你不想链接器忽略它,可在你的库中定义一个函数:

__declspec(dllexport) int PullInMyLibrary() {return 0;}

如果你把你的测试用例放在一个静态库中,__declspec(dllexport)可以不用写。

然后,在主程序中,激活函数:

int PullInMyLibrary();
static int dummy = PullInMyLibrary();
这样你的测试用例会被引用,并且在启动时会进行注册。

另外,如果把测试用例编译成了一个静态库,添加/OPT:NOREF在你的主程序的链接器的选项中。如果使用了MSVC++ IDE,找到exe程序,在属性/配置 属性/链接器/优化中将References设置修改为keep Unreferenced Data(/OPT:NOREF).

还有一点,如果你的测试程序被编写为静态库,Google Test的库也必须为静态库;或者都会动态库,否则可能不会被正常执行,或者可能根本不会被运行。总之:为了使你的生活更加简单--别把你的测试程序写成库的形式。

后续

恭喜你,你已经掌握了Google Test的基础知识。你可以开始编写自己的Google Test测试用例了,可以多阅读一些用例,或者继续阅读AdvancedGuide,你将会掌握得更好。

限制

Google Test被设计为线程完全。目前仅在提供pthreads库的平台才实现了线程安全。其它平台,如windows是不安全的。你可以自己实现线程同步in gtest-port.h为你使用的平台。










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值