GoogleTest入门

一、定义

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)。

1.1 gtest

单元测试框架通常会对一些变量或函数设置期望,若变量值或返回值符合预期,就认为单元测试用例通过。gtest也提供了下面一些断言:

ASSERT_* 系列的断言,当检查点失败时,立即退出单元测试; EXPECT_* 系列的断言,当检查点失败时,单元测试还是会继续执行,但结束后会标记所有 ECPECT_*失败的用例; EXPECT_CALL 设置函数调用之后期望的实现,比如直接返回某一个值。该断言后面没有 .Times()时,无论函数有没有调用都不会导致失败,如果有 .Times()时,不满足 .Times()设置的次数时就会导致期望失败

1.2 gmock

有时候对于一些接口,比如向服务器发送请求。但单元测试中有没有可用于测试的服务器,这个时候就需要mock这个请求接口。 mock工具的作用是指定函数的行为(模拟函数的行为)。可以对入参进行校验,对出参进行设定,还可以指定函数的返回值。

Mock的基本使用方法是:

  1. 继承某一个类;

  1. 实现或重写类中的某个或某些虚方法;

  1. 创建Mock对象,设置重写方法的实现(大部分是直接返回,对于返回值是内置类型,即使不设置调用后的期望幸会,gmock也会设置默认返回值);

  1. 调用被测接口,Mock对象调用重写方法,期望满足,测试通过。

二、下载GoogleTest

googletest,最新版本的仅支持c++14及以上,c++11请安装旧版本

三、添加googletest

  1. 将整个文件解压到工程指定目录下

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)

四、函数定义及相关说明

4.1 TEST

TEST(test_case_name, test_name)

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

4.2 TEST_F

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之后运行

4.3 InitGoogleTest

::testing::InitGoogleTest(&argc,argv)

解析 googletest 标记的命令行参数,并移除所有已识别的标记。这允许用户通过不同的标记控制测试程序的行为,我们将在AdvancedGuide(高级教程)中描述相关的内容。你必须在调用RUN_ALL_TESTS()之前调用这个函数,否则标记将无法得到适当的初始化。

在Windows下,InitGoogleTest()同样也可以基于宽字符串使用,因此它也可以被用于以UNICODE模式编译的程序。

你可能觉得编写一个这样的main函数太麻烦了,我们觉得也是,这就是Google Test 提供了一个基础的main函数实现的原因。如果它能够满足你的需求的话,仅需要将你的测试用例和库gtest_main链接就可以了。

注:ParseGUnitFlags()已弃用,推荐使用InitGoogleTest()。

4.4 RUN_ALL_TESTS

RUN_ALL_TESTS()

运行所有测试案例

4.1 测试宏定义

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)

五、死亡测试

5.1 定义

通常在测试过程中,我们需要考虑各种各样的输入,有的输入可能直接导致程序崩溃,这时我们就需要检查程序是否按照预期的方式挂掉,这也就是所谓的“死亡测试”。gtest的死亡测试能做到在一个安全的环境下执行崩溃的测试案例,同时又对崩溃结果进行验证。

5.2 宏定义

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下的不同。

5.3 宏定义说明

5.3.1 *_DEATH(statement, regex`)

statement是被测试的代码语句

regex是一个正则表达式,用来匹配异常时在stderr中输出的内容

如下面的例子:

voidFoo(){
    int *pInt = 0;
    *pInt = 42 ;
}

TEST(FooDeathTest, Demo)
{
    EXPECT_DEATH(Foo(), "");
}

ps:编写死亡测试案例时,TEST的第一个参数,即testcase_name,请使用DeathTest后缀。原因是gtest会优先运行死亡测试案例,应该是为线程安全考虑。

5.3.2 *_EXIT(statement, predicate, regex`)

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),  "");
}

5.3.3 *_DEBUG_DEATH

#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
}

5.4 正则表达式

在POSIX系统(Linux, Cygwin, 和 Mac)中,gtest的死亡测试中使用的是POSIX风格的正则表达式,想了解POSIX风格表达式可参考:

  1. POSIX extended regular expression

  1. 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

5.5 死亡测试运行方式

1.fast方式(默认的方式)

testing::FLAGS_gtest_death_test_style = "fast";
  1. 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();
}

5.6 注意事项

  1. 不要在死亡测试里释放内存。

  1. 在父进程里再次释放内存。

  1. 不要在程序中使用内存堆检查。

六、值参数化测试

有些时候,我们需要对代码实现的功能使用不同的参数进行测试,比如使用大量随机值来检验算法实现的正确性,或者比较同一个接口的不同实现之间的差别。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 定义在 <limits.h> 文件中
}
 
// 测试 正输入(>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<structparamList>
{

};
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种事件:

  1. 全局事件:要实现全局事件,必须写一个类,继承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();
}
  1. 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 
}
  1. 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博客

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值