GTest 总结

Google C++单元测试框架(简称Gtest),可在多个平台上使用(包括Linux, Mac OS X, Windows, Cygwin和Symbian),它提供了丰富的断言、致命和非致命失败判断,能进行值参数化测试、类型参数化测试、“死亡测试”。

1断言

一般的,要测试一个方法(函数)是否是正常执行的,可以提供一些输入数据,在调用这个方法(函数)后,得到输出数据,然后检查输出的数据是否与我们期望的结果是一致的,若一致,则说明这个方法的逻辑是正确的,否则,就有问题。  在对输出结果进行检查(check)时,Gtest为我提供了一系列的断言(assertion)来进行代码测试,这些宏有点类似于函数调用。当断言失败时Gtest将会打印出assertion时的源文件和出错行的位置,以及附加的失败信息。这些输出的附加信息用户可以直接通过“<<”在这些断言宏后面。

Gtest中,断言的宏可以理解为分为两类,一类是ASSERT系列,一类是EXPECT系列。

ASSERT_* 系列的断言(Fatal assertion),当检查点失败时,退出当前函数(注意:并非退出当前案例)。

EXPECT_* 系列的断言(Nonfatal assertion),当检查点失败时,继续执行下一个检查点(每一个断言表示一个测试点)。

通常情况应该首选使用EXPECT_,因为ASSERT_*在报告完错误后不会进行清理工作,有可能导致内存泄露问题。

1.1布尔型检查

Fatal assertion

Nonfatal assertion

Verifies

ASSERT_TRUE(condition);

EXPECT_TRUE(condition);

condition is true

ASSERT_FALSE(condition);

EXPECT_FALSE(condition);

condition is false

1.2二值检查

Fatal assertion

Nonfatal assertion

Verifies

ASSERT_EQ(expected, actual);

EXPECT_EQ(expected, actual);

expected == actual

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++大部分原生类型都是可以使用二进制对比的。但是对于自定义类型,我们就要定义一些操作符的行为,比如=、<等。

1.3字符串检查

Fatal assertion

Nonfatal assertion

Verifies

ASSERT_STREQ(expected_str, actual_str);

EXPECT_STREQ(expected_str,actual_str);

the two C strings have the same content

ASSERT_STRNE(str1, str2);

EXPECT_STRNE(str1, str2);

the two C strings have different content

ASSERT_STRCASEEQ(expected_str, actual_str);

EXPECT_STRCASEEQ(expected_str, actual_str);

the two C strings have the same content, ignoring case (忽略大小写)

ASSERT_STRCASENE(str1, str2);

EXPECT_STRCASENE(str1, str2);

the two C strings have different content, ignoring case (忽略大小小)

*STREQ*和*STRNE*同时支持char*和wchar_t*类型的,*STRCASEEQ*和*STRCASENE*却只接收char*

1.4异常检查

Fatal assertion

Nonfatal assertion

Verifies

ASSERT_THROW(statement, exception_type);

EXPECT_THROW(statement, exception_type);

statement throws an exception of the given type

ASSERT_ANY_THROW(statement);

EXPECT_ANY_THROW(statement);

statement throws an exception of any type

ASSERT_NO_THROW(statement);

EXPECT_NO_THROW(statement);

statement doesn't throw any exception

1.5浮点检查

Fatal assertion

Nonfatal assertion

Verifies

ASSERT_FLOAT_EQ(expected, actual);

EXPECT_FLOAT_EQ(expected, actual);

the two float values are almost equal

ASSERT_DOUBLE_EQ(expected, actual);

EXPECT_DOUBLE_EQ(expected, actual);

the two double values are almost equal

在对比数据方面,我们往往会讨论到浮点数的对比。因为在一些情况下,浮点数的计算精度将影响对比结果,所以这块都会单独拿出来说。GTest对于浮点数的对比也是单独的

1.6相近值检查

Fatal assertion

Nonfatal assertion

Verifies

ASSERT_NEAR(val1, val2, abs_error);

EXPECT_NEAR(val1, val2, abs_error);

the difference between val1 and val2 doesn't exceed the given absolute error

2 宏测试

2.1 TEST宏        

TEST宏是一个很重要的宏,它构成一个测试特例,它的原型是

 TEST宏的第一个参数是test_suite_name(测试套件名),第二个参数是test_name(测试特例名)。测试套件名和测试特例名(也叫测试名)的区别和联系:

测试套件(Test Case)是为某个特殊目标而编制的一组测试输入、执行条件以及预期结果,以便测试某个程序路径或核实是否满足某个特定需求,测试特例是测试套件下的一个(组)测试。

GTest 总结_第1张图片

GTest 总结_第2张图片

以上述代码为例,三段TEST宏构成的是一个测试套件——测试套件名是FactorialTest(阶乘方法检测,测试Factorial函数),该用例覆盖了三种测试特例——Negative、Zero和Positive——即检测输入参数是负数、零和正数这三种特例情况。

对于测试套件名和测试特例名,不能有下划线(_)。因为GTest源码中需要使用下划线把它们连接成一个独立的类名

这样也就要求,我们不能有相同的“测试套件名和特例名”的组合——否则类名重合。

测试套件名和测试特例名的分开,使得我们编写的测试代码有着更加清晰的结构——即有相关性也有独立性。相关性是通过相同的测试套件名联系的,而独立性通过不同的测试特例名体现的。

2.2 TEST_F 宏

测试PositiveNum这个类。

GTest 总结_第3张图片

使用TEST_F前需要创建一个固件类,继承于::testing::Test类。在类内部使用public或者protected描述其成员,为了保证实际执行的测试子类可以使用其成员变量。在构造函数或者继承于::testing::Test类中的SetUp方法中,可以实现我们需要构造的数据。在析构函数或者继承于::testing::Test类中的TearDown方法中,可以实现一些资源释放的代码。“构造函数/析构函数”和“SetUp/TearDown”的选择,对于什么时候选择哪对,没有统一的标准。一般来说就是构造/析构函数里忌讳做什么就不要在里面做,比如抛出异常等。

GTest 总结_第4张图片

TEST_F宏和TEST宏的实现非常接近,只是TEST_F宏的封装更加开放一些,所以对TEST宏的功能多了一些扩展。他的原型是:

第一个参数为测试套件名(与创建的固件类名一致),第二个为测试名,可任意取。

    GTest 总结_第5张图片

GTest 总结_第6张图片

第一个测试中,修改了pn1成员数据值为-2,测试结果为ok,紧接着第二个测试结果也是ok(如果测试一的修改会影响测试二,结果应为fail)。所有局部测试都是正确的,验证了固件类中数据的恒定性,每个测试特例都是要新建一个新的PositiveNumTest对象,并在该测试特例结束时销毁它,这样可以保证数据的干净。

TEST_F与TEST的区别是,TEST_F提供了一个初始化函数(SetUp)和一个清理函数(TearDown),在TEST_F中使用的变量可以在初始化函数SetUp中初始化,在TearDown中销毁,并且所有的TEST_F是互相独立的,都是在初始化以后的状态开始运行,一个TEST_F不会影响另一个TEST_F所使用的数据,多个测试场景需要相同数据配置的情况,用 TEST_F。

2.3 TEST_P宏

在设计测试案例时,经常需要考虑给被测函数传入不同的值的情况。我们之前的做法通常是写一个通用方法,然后编写在测试案例调用它。即使使用了通用方法,这样的工作也是有很多重复性的。

测试IsPrime这个函数(判断输入值是否为质数)。

GTest 总结_第7张图片

用TEST这个宏,需要编写如下的测试案例,每输入一个值就需要写一个测试点,这还只是在一个测试中,如果把每个测试点单独创建一个测试,工作量就更大。

GTest 总结_第8张图片

 使用TEST_P这个宏,对输入进行参数化,就简单很多。

GTest 总结_第9张图片

GTest 总结_第10张图片

创建一个参数化类IsPrimeParamTest。继承自TestWithParam这个模板类,TestWithParam又继承Test和WithParamInterface

WithParamInterface这个模板类定义了ParamType,用于参数型别推导,提供GetParam()函数,用于TEST_P宏里的实现逻辑获取参数。

GTest 总结_第11张图片

使用INSTANTIATE_TEST_CASE_P这宏来告诉gtest你要测试的参数范围:

第一个参数PARAM是测试案例的前缀,可以任意取。

第二个参数是测试案例的名称,需要和之前定义的参数化的类的名称相同,如:IsPrimeParamTest

第三个参数是可以理解为参数生成器,上面的例子使用test::Values表示使用括号内的参数。Google提供了一系列的参数生成的函数:

Range(begin,end[,step])

范围在begin~end之间,步长为step,不包括end

Values(v1, v2, ..., vN)

v1,v2到vN的值

ValuesIn(container) /ValuesIn(begin, end)

从一个C类型的数组或是STL容器,或是迭代器中取值

Bool()

取false 和 true 两个值

Combine(g1, g2, ..., gN)

它将g1,g2,...gN进行排列组合,g1,g2,...gN本身是一个参数生成器,每次分别从g1,g2,..gN中各取出一个值,组合成一个元组(Tuple)作为一个参数。

TEST_P中两个参数,第一个为测试套件名(与创建的测试类名一致),第二个为测试特例名称。

GTest 总结_第12张图片

3. 预处理事件机制

gtest 提供了多种预处理事件机制,非常方便我们在测试之前或之后做一些操作。

1. 全局的,所有测试执行前后。

2. TestSuite级别的,在某测试套件中第一个测试前,最后一个测试执行后。

3. TestCase级别的,每个测试前后。

3.1全局事件

要实现全局事件,必须写一个类,继承testing::Environment类,实现里面的SetUp和TearDown方法。

1. SetUp()方法在所有案例执行前执行

2. TearDown()方法在所有案例执行后执行

GTest 总结_第13张图片

还需要在main函数中通过调用testing::AddGlobalTestEnvironment这个函数将事件挂进来,也就是说,我们可以写很多个这样的类,然后将他们的事件都挂上去,AddGlobalTestEnvironment这个函数要放在RUN_ALL_TEST之前。

 GTest 总结_第14张图片

3.2 TestSuites事件

需要写一个类,继承testing::Test,然后实现两个静态方法

1. SetUpTestCase() 方法在第一个TestCase之前执行

2. TearDownTestCase() 方法在最后一个TestCase之后执行

GTest 总结_第15张图片

 GTest 总结_第16张图片

3.3 TestCase事件

TestCase事件是挂在每个案例执行前后的,实现方式和Test'Suites的几乎一样,不过需要实现的是SetUp方法和TearDown方法:

1. SetUp()方法在每个TestCase之前执行

2. TearDown()方法在每个TestCase之后执行。

GTest 总结_第17张图片

 GTest 总结_第18张图片

 

4. 测试用例运行入口

    GTest 总结_第19张图片

RUN_ALL_TESTS()这个宏,从名字上来看,就是运行所有的测试用例,这才是我们运行测试用例的真正入口。它的原型是:

GTest 总结_第20张图片 

RUN_ALL_TESTS()是一个宏,将其实现为函数,在这里,调用了UnitTest单例的Run函数,看调用过程,可以看到,依次调用的过程是

1.UnitTest::Run()

2. UnitTestImpl::RunAllTests()

3. TestCase::Run()

4. TestInfo::Run()

5. Test::Run()

5. 参考文档

【1】google mock 介绍

【2】google test 介绍 

你可能感兴趣的:(C++杂谈,c++)