Google C++ Testing Framework(简称gtest,http://code.google.com/p/googletest/)是Google公司发布的一个开源C/C++单元测试框架,已被应用于多个开源项目及Google内部项目中,知名的例子包括Chrome Web浏览器、LLVM编译器架构、Protocol Buffers数据交换格式及工具等。
优秀的C/C++单元测试框架并不算少,相比之下gtest仍具有明显优势。与CppUnit比,gtest需要使用的头文件和函数宏更集中,并支持测试用例的自动注册。与CxxUnit比,gtest不要求Python等外部工具的存在。与Boost.Test比,gtest更简洁容易上手,实用性也并不逊色。Wikipedia给出了各种编程语言的单元测试框架列表(http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks)。
单元测试框架通常会对一些变量或函数设置期望,若变量值或返回值符合预期,就认为单元测试用例通过。gtest也提供了下面一些断言:
ASSERT_* 系列的断言,当检查点失败时,立即退出单元测试; EXPECT_* 系列的断言,当检查点失败时,单元测试还是会继续执行,但结束后会标记所有 ECPECT_*失败的用例; EXPECT_CALL 设置函数调用之后期望的实现,比如直接返回某一个值。该断言后面没有 .Times()时,无论函数有没有调用都不会导致失败,如果有 .Times()时,不满足 .Times()设置的次数时就会导致期望失败
有时候对于一些接口,比如向服务器发送请求。但单元测试中有没有可用于测试的服务器,这个时候就需要mock这个请求接口。 mock工具的作用是指定函数的行为(模拟函数的行为)。可以对入参进行校验,对出参进行设定,还可以指定函数的返回值。
Mock的基本使用方法是:
继承某一个类;
实现或重写类中的某个或某些虚方法;
创建Mock对象,设置重写方法的实现(大部分是直接返回,对于返回值是内置类型,即使不设置调用后的期望幸会,gmock也会设置默认返回值);
调用被测接口,Mock对象调用重写方法,期望满足,测试通过。
googletest,最新版本的仅支持c++14及以上,c++11请安装旧版本
将整个文件解压到工程指定目录下
2、修改CMakelist.txt
添加
#添加库目录
add_subdirectory(./library/googletest)
#添加头文件
include_directories(
./library/googletest/googletest/include
./library/googletest/googlemock/include
)
#添加库文件
link_directories(
./library/googletest
)
#连接目标文件
TARGET_LINK_LIBRARIES(${PROJECT_NAME} gtest gtest_main)
TEST(test_case_name, test_name)
TEST宏的作用是创建一个简单测试,它定义了一个测试函数,在这个函数里可以使用任何C++代码并使用提供的断言来进行检查。
TEST_F(test_fixture, test_name)
fixture,其语义是固定的设施,而test fixture在gtest中的作用就是为每个TEST都执行一些同样的操作。
TEST_F与TEST的区别是,TEST_F提供了一个初始化函数(SetUp)和一个清理函数(TearDown),在TEST_F中使用的变量可以在初始化函数SetUp中初始化,在TearDown中销毁,并且所有的TEST_F是互相独立的,都是在初始化以后的状态开始运行,一个TEST_F不会影响另一个TEST_F所使用的数据。即进行资源初始化工作,进行资源复用,最后回收资源的场景。
TEST_F就是完成这样的事情,它的第一个参数TestFixtureName是个类,需要继承testing::Test,同时根据需要实现以下两个虚函数:
virtual void SetUp():在TEST_F中测试案例之前运行;
virtual void TearDown():在TEST_F之后运行。
可以类比对象的构造函数和析构函数。这样,同一个TestFixtureName下的每个TEST_F都会先执行SetUp,最后执行TearDwom。
此外,testing::Test还提供了两个static函数:
static void SetUpTestSuite():在第一个TEST之前运行
static void TearDownTestSuite():在最后一个TEST之后运行
::testing::InitGoogleTest(&argc,argv)
解析 googletest 标记的命令行参数,并移除所有已识别的标记。这允许用户通过不同的标记控制测试程序的行为,我们将在AdvancedGuide(高级教程)中描述相关的内容。你必须在调用RUN_ALL_TESTS()之前调用这个函数,否则标记将无法得到适当的初始化。
在Windows下,InitGoogleTest()同样也可以基于宽字符串使用,因此它也可以被用于以UNICODE模式编译的程序。
你可能觉得编写一个这样的main函数太麻烦了,我们觉得也是,这就是Google Test 提供了一个基础的main函数实现的原因。如果它能够满足你的需求的话,仅需要将你的测试用例和库gtest_main链接就可以了。
注:ParseGUnitFlags()已弃用,推荐使用InitGoogleTest()。
RUN_ALL_TESTS()
运行所有测试案例
ASSERT:致命失败,函数被中止
EXPECT:非致命性失败,函数不被中止
google推荐使用EXPECT_* 宏,因为当测试定义多个断言时,它们允许测试继续进行。一个断言宏需要两个参数。第一个参数是测试组的名称(一个可自由选择的字符串),第二个参数是测试本身的名称。Generator库只是定义了函数generate(...),因此本文中的测试属于同一个组。
断言:布尔值检查、数值型数据检查、字符串检查、显示成功或失败、异常检查、Predicate Assertions、浮点型检查、Windows HRESULT assertions、类型检查。
ASSERT宏 |
EXPECT宏 |
功能 |
ASSERT_TRUE |
EXPECT_TRUE |
判真 |
ASSERT_FALSE |
EXPECT_FALSE |
判假 |
ASSERT_EQ |
EXPECT_EQ |
相等 |
ASSERT_NE |
EXPECT_NE |
不等 |
ASSERT_GT |
EXPECT_GT |
大于 |
ASSERT_LT |
EXPECT_LT |
小于 |
ASSERT_GE |
EXPECT_GE |
大于或等于 |
ASSERT_LE |
EXPECT_LE |
小于或等于 |
ASSERT_FLOAT_EQ |
EXPECT_FLOAT_EQ |
单精度浮点值相等 |
ASSERT_DOUBLE_EQ |
EXPECT_DOUBLE_EQ |
双精度浮点值相等 |
ASSERT_NEAR |
EXPECT_NEAR |
浮点值接近(第3个参数为误差阈值) |
ASSERT_STREQ |
EXPECT_STREQ |
C字符串相等 |
ASSERT_STRNE |
EXPECT_STRNE |
C字符串不等 |
ASSERT_STRCASEEQ |
EXPECT_STRCASEEQ |
C字符串相等(忽略大小写) |
ASSERT_STRCASENE |
EXPECT_STRCASENE |
C字符串不等(忽略大小写) |
ASSERT_PRED1 |
EXPECT_PRED1 |
自定义谓词函数,(pred, arg1)(还有_PRED2, …,_PRED5) |
异常测试
C程序中要返回出错信息,可以利用特定的函数返回值、函数的输出(outbound)参数、或者设置全局变量(如C标准库定义的 errno,Windows API中的“上次错误”(last error)代码,Winsock中与每个socket相关联的错误代码)。C++程序常用异常(exception)来返回出错信息,gtest为异常测试提供了专用的测试宏:
ASSERT宏 |
EXPECT宏 |
功能 |
ASSERT_NO_THROW |
EXPECT_NO_THROW |
不抛出异常,参数为(statement) |
ASSERT_ANY_THROW |
EXPECT_ANY_THROW |
抛出异常,参数为(statement) |
ASSERT_THROW |
EXPECT_THROW |
抛出特定类型的异常,参数为(statement, type) |
通常在测试过程中,我们需要考虑各种各样的输入,有的输入可能直接导致程序崩溃,这时我们就需要检查程序是否按照预期的方式挂掉,这也就是所谓的“死亡测试”。gtest的死亡测试能做到在一个安全的环境下执行崩溃的测试案例,同时又对崩溃结果进行验证。
Fatal assertion |
Nonfatal assertion |
Verifies |
ASSERT_DEATH(statement, regex`); |
EXPECT_DEATH(statement, regex) ; |
statement crashes with the given error |
ASSERT_EXIT(statement, predicate, regex`); |
EXPECT_EXIT(statement, predicate, regex); |
statement exits with the given error and its exit code matches predicate |
由于有些异常只在Debug下抛出,因此还提供了*_DEBUG_DEATH,用来处理Debug和Realease下的不同。
statement是被测试的代码语句
regex是一个正则表达式,用来匹配异常时在stderr中输出的内容
如下面的例子:
voidFoo(){
int *pInt = 0;
*pInt = 42 ;
}
TEST(FooDeathTest, Demo)
{
EXPECT_DEATH(Foo(), "");
}
ps:编写死亡测试案例时,TEST的第一个参数,即testcase_name,请使用DeathTest后缀。原因是gtest会优先运行死亡测试案例,应该是为线程安全考虑。
statement是被测试的代码语句
predicate 在这里必须是一个委托,接收int型参数,并返回bool。只有当返回值为true时,死亡测试案例才算通过。gtest提供了一些常用的predicate:
testing::ExitedWithCode(exit_code)
如果程序正常退出并且退出码与exit_code相同则返回 true
testing::KilledBySignal(signal_number) // Windows下不支持
regex是一个正则表达式,用来匹配异常时在stderr中输出的内容
这里, 要说明的是,_DEATH其实是对_EXIT进行的一次包装,*_DEATH的predicate判断进程是否以非0退出码退出或被一个信号杀死。
举例:
TEST(ExitDeathTest, Demo)
{
EXPECT_EXIT(_exit(1), testing::ExitedWithCode(1), "");
}
#ifdef#define EXPECT_DEBUG_DEATH(statement, regex)do { statement; } while (false)
#define ASSERT_DEBUG_DEATH(statement, regex)do { statement; } while (false)
#else#define EXPECT_DEBUG_DEATH(statement, regex)EXPECT_DEATH(statement, regex)
#define ASSERT_DEBUG_DEATH(statement, regex)ASSERT_DEATH(statement, regex)
#endif// NDEBUG for EXPECT_DEBUG_DEATH
可以从定义中看到,在Debug版和Release版本下, *_DEBUG_DEATH的定义不一样。因为很多异常只会在Debug版本下抛出,而在Realease版本下不会抛出,所以针对Debug和Release分别做了不同的处理。看gtest里自带的例子就明白了:
intDieInDebugElse12(int* sideeffect){
if (sideeffect) *sideeffect = 12;
#ifndef NDEBUGGTEST_LOG_(FATAL, "debug death inside DieInDebugElse12()");
#endif// NDEBUG return12;
}
TEST(TestCase, TestDieOr12WorksInDgbAndOpt)
{
int sideeffect = 0;
// Only asserts in dbg. EXPECT_DEBUG_DEATH(DieInDebugElse12(&sideeffect), "death");
#ifdef NDEBUG // opt-mode has sideeffect visible.EXPECT_EQ(12, sideeffect);
#else// dbg-mode no visible sideeffect. EXPECT_EQ(0, sideeffect);
#endif
}
在POSIX系统(Linux, Cygwin, 和 Mac)中,gtest的死亡测试中使用的是POSIX风格的正则表达式,想了解POSIX风格表达式可参考:
POSIX extended regular expression
Wikipedia entry.
在Windows系统中,gtest的死亡测试中使用的是gtest自己实现的简单的正则表达式语法。 相比POSIX风格,gtest的简单正则表达式少了很多内容,比如 ("x|y"), ("(xy)"), ("[xy]") 和("x{5,7}")都不支持。
gtest定义两个宏,用来表示当前系统支持哪套正则表达式风格:
POSIX风格:GTEST_USES_POSIX_RE = 1
Simple风格:GTEST_USES_SIMPLE_RE=1
1.fast方式(默认的方式)
testing::FLAGS_gtest_death_test_style = "fast";
threadsafe方式
testing::FLAGS_gtest_death_test_style = "threadsafe";
你可以在 main() 里为所有的死亡测试设置测试形式,也可以为某次测试单独设置。Google Test会在每次测试之前保存这个标记并在测试完成后恢复,所以你不需要去管这部分工作 。如:
TEST(MyDeathTest, TestOne) {
testing::FLAGS_gtest_death_test_style = "threadsafe";
// This test is run in the "threadsafe" style:ASSERT_DEATH(ThisShouldDie(), "");
}
TEST(MyDeathTest, TestTwo) {
// This test is run in the "fast" style:
ASSERT_DEATH(ThisShouldDie(), "");
}
intmain(int argc, char** argv){
testing::InitGoogleTest(&argc, argv);
testing::FLAGS_gtest_death_test_style = "fast";
returnRUN_ALL_TESTS();
}
不要在死亡测试里释放内存。
在父进程里再次释放内存。
不要在程序中使用内存堆检查。
有些时候,我们需要对代码实现的功能使用不同的参数进行测试,比如使用大量随机值来检验算法实现的正确性,或者比较同一个接口的不同实现之间的差别。gtest把“集中输入测试参数”的需求抽象出来提供支持,称为值参数化测试(Value Parameterized Test)。
// 判定是否为质数,如果n是质数,则返回truebool IsPrime(int n)
{
// 判断1: 小数if (n <=1) returnfalse;
// 判断1: 偶数(2是质数)if (n %2==0) return n ==2;
// 现在 n 奇数且 n >= 3.// 从 3 开始, 用每一个奇数 i去整除n,直到 n < i^2for (int i =3; ; i +=2) {
if (i > n/i) break;
// 否则,n如果被i整除,就不是质数if (n % i ==0) returnfalse;
}
// n在范围(1,n)中没有整数因子,因此是素数。returntrue;
}
原方案:
// --------------------<测试 IsPrime()>---------------------------------// 测试负数是否为质数(也称素数)TEST(IsPrimeTest, Negative) {
// 这个测试属于IsPrimeTest测试用例EXPECT_FALSE(IsPrime(-1));
EXPECT_FALSE(IsPrime(-2));
EXPECT_FALSE(IsPrime(INT_MIN)); // INT_MIN 定义在 文件中
}
// 测试 正输入(>0)TEST(IsPrimeTest, Positive) {
EXPECT_FALSE(IsPrime(0));
EXPECT_FALSE(IsPrime(1));
EXPECT_TRUE(IsPrime(2));
EXPECT_TRUE(IsPrime(3));
EXPECT_FALSE(IsPrime(4));
EXPECT_TRUE(IsPrime(5));
EXPECT_FALSE(IsPrime(6));
EXPECT_FALSE(IsPrime(9));
EXPECT_TRUE(IsPrime(11));
EXPECT_TRUE(IsPrime(23));
}
参数化方案:
// --------------------<测试 参数化>-----------------------------------usingnamespace ::testing;
structparamList
{
bool out;
int in;
};
class IsPrimeParamTest : public ::testing::TestWithParam
{
};
TEST_P(IsPrimeParamTest, IsPrime)
{
bool out = GetParam().out;
int in = GetParam().in;
EXPECT_EQ(out, IsPrime(in));
}
INSTANTIATE_TEST_CASE_P(IsPrimeParamTest,
IsPrimeParamTest,
Values(paramList{false, 10}, paramList{true, 3}));
首先,我们定义一个结构体 paramList,然后用它作为参数化的类型。使用结构体的原因是把入力和预期值一起传进去。
从 ::testing::TestWithParam 派生一个测试夹具类 IsPrimeParamTest。
然后,使用 TEST_P,声明测试代码,如上所示,简洁了很多。
现在,我们已经准备好了测试夹具 IsPrimeParamTest 和测试用例,那么,接下来我们就需要实例化这个测试用例。在实例化的过程中, 我们要告诉 gtest 要测试的范围:
INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator, ...)
第一个参数,是测试用例的前缀,可以任意取;
第二个参数,是测试用例的名称,需要和之前定义的参数化的类名称相同,比如:IsPrimeParamTest;
第三个参数,可以理解为参数生成器,上面的例子使用了 testing::Values 函数,它的参数是结构体 paramList (这里是自定义的);
最后一个可选参数允许用户指定一个函数或函子,用来基于测试参数产生用户测试名称后缀(_suffix)。这个函数必须接受一个类型为 testing::TestParamInfo 的参数,并返回 std::string 类型的值。
注意: 测试名称必须是非空,唯一,且只能包含ASCII的字母、数字或下划线。
函数 |
说明 |
Range(begin, end[, step]) |
2个字符串相同 |
Values(v1, v2, …, vN) |
2个字符串不同 |
ValuesIn(container)和ValuesIn(begin, end) |
忽略大小写,2个字符串相同 |
Bool() |
取false和true两个值 |
Combine(g1, g2, …, gN) |
每次分别从g1,g2,…gN中取出一个值,组合成一个元组(Tuple)作为一个参数。 |
*ps:Combine(g1, g2, …, gN), 这个功能只在提供了 tr1/tuple>头的系统中有效。gtest会自动去判断是否支持 tr/tuple,如果你的系统确实支持,而gtest判断错误的话,你可以重新定义宏GTEST_HAS_TR1_TUPLE=1。
gtest分3种事件:
全局事件:要实现全局事件,必须写一个类,继承testing::Environment类,实现里面的SetUp和TearDown方法。SetUp方法在所有案例执行前执行;TearDown方法在所有案例执行后执行。
classFooEnvironment :public testing::Environment
{
public:
virtualvoidSetUp(){
std::cout << "Foo FooEnvironment SetUP" << std::endl;
}
virtualvoidTearDown(){
std::cout << "Foo FooEnvironment TearDown" << std::endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
testing::AddGlobalTestEnvironment(new FooEnvironment);
testing::InitGoogleTest(&argc, argv);
returnRUN_ALL_TESTS();
}
TestSuit事件:需要写一个类,继承testing::Test,然后实现两个静态方法:
(1)、SetUpTestCase方法在第一个TestCase之前执行;
(2)、TearDownTestCase方法在最后一个TestCase之后执行。
classFooTest :public testing::Test {
protected:
staticvoidSetUpTestCase(){
shared_resource_ = new ;
}
staticvoidTearDownTestCase(){
delete shared_resource_;
shared_resource_ = NULL;
}
// Some expensive resource shared by all tests.static T* shared_resource_;
};
//在编写测试案例时,我们需要使用TEST_F这个宏,第一个参数必须是我们上面类的名字,代表一个TestSuite。TEST_F(FooTest, Test1)
{
// you can refer to shared_resource here
}
TEST_F(FooTest, Test2)
{
// you can refer to shared_resource here
}
TestCase事件:是挂在每个案例执行前后的,需要实现的是SetUp方法和TearDown方法。
(1)、SetUp方法在每个TestCase之前执行;
(2)、TearDown方法在每个TestCase之后执行。
classFooCalcTest:public testing::Test
{
protected:
virtual void SetUp()
{
m_foo.Init();
}
virtual void TearDown()
{
m_foo.Finalize();
}
FooCalc m_foo;
};
TEST_F(FooCalcTest, HandleNoneZeroInput)
{
EXPECT_EQ(4, m_foo.Calc(12, 16));
}
TEST_F(FooCalcTest, HandleNoneZeroInput_Error)
{
EXPECT_EQ(5, m_foo.Calc(12, 16));
}
每个基于gtest的测试过程,是可以分为多个TestSuite级别,而每个TestSuite级别又可以分为多个TestCase级别。这样分层的结构的好处,是可以针对不同的TestSuite级别或者TestCase级别设置不同的参数、事件机制等,并且可以与实际测试的各个模块层级相互对应,便于管理。
可以通过操作符"<<"将一些自定义的信息输出,如下举例
TEST(TestCase,test1){
ASSERT_TRUE(quickJS())<<"quickJS函数异常";
}
当ASSERT_TRUE不成立时,运行窗口会输出quickJS函数异常
编写死亡测试案例时,TEST的第一个参数,即test_case_name,请使用DeathTest后缀,原因是gtest会优先运行死亡测试案例,应该是为线程安全考虑。
testing::AddGlobalTestEnvironment(newFooEnvironment):在main函数中创建和注册全局环境对象。
对于运行参数,gtest提供了三种设置的途:
(1)、系统环境变量;(2)、命令行参数;(3)、代码中指定FLAG。
命令行参数:
(1)、--gtest_list_tests:使用这个参数时,将不会执行里面的测试案例,而是输出一个案例的列表;
(2)、--gtest_filter:对执行的测试案例进行过滤,支持通配符;
(3)、--gtest_also_run_disabled_tests:执行案例时,同时也执行被置为无效的测试案例;
(4)、--gtest_repeat=[COUNT]:设置案例重复运行次数;
(5)、--gtest_color=(yes|no|auto):输出命令行时是否使用一些五颜六色的颜色,默认是auto;
(6)、--gtest_print_time:输出命令时是否打印每个测试案例的执行时间,默认是不打印的;
(7)、--gtest_output=xml[:DIRECTORY_PATH\|:FILE_PATH:将测试结果输出到一个xml中,如—gtest_output=xml:d:\foo.xml 指定输出到d:\foo.xml ,如果不是指定了特定的文件路径,gtest每次输出的报告不会覆盖,而会以数字后缀的方式创建;
(8)、--gtest_break_on_failure:调试模式下,当案例失败时停止,方便调试;
(9)、--gtest_throw_on_failure:当案例失败时以C++异常的方式抛出;
(10)、--gtest_catch_exceptions:是否捕捉异常,gtest默认是不捕捉异常的,这个参数只在Windows下有效。
Google Test被设计为线程安全的。在pthread库可用的系统上是线程安全的。目前,在其他系统(例如Windows)上从两个线程并发使用Google Test断言是不安全的。在大多数测试中,这不是一个问题,因为断言通常是在主线程中完成的。如果您想提供帮助,可以自愿在gtest-port.h中为您的平台实现必要的同步原语。
手把手教你使用gtest写单元测试(1/2) - 知乎 (zhihu.com)
C++单元测试框架-gtest-3-参数化_instantiate_test_case_p_tupelo-shen的博客-CSDN博客
GTest笔记 - 爱码网 (likecs.com)
Google单元测试工具gtest和gmoke简介 - 灰信网(软件开发博客聚合) (freesion.com)
【gTest】gtest简介及简单使用_伐尘的博客-CSDN博客