发布时间:2024-01-01 19:00
Google C++单元测试框架(简称Gtest),可在多个平台上使用(包括Linux, Mac OS X, Windows, Cygwin和Symbian),它提供了丰富的断言、致命和非致命失败判断,能进行值参数化测试、类型参数化测试、“死亡测试”。
一般的,要测试一个方法(函数)是否是正常执行的,可以提供一些输入数据,在调用这个方法(函数)后,得到输出数据,然后检查输出的数据是否与我们期望的结果是一致的,若一致,则说明这个方法的逻辑是正确的,否则,就有问题。 在对输出结果进行检查(check)时,Gtest为我提供了一系列的断言(assertion)来进行代码测试,这些宏有点类似于函数调用。当断言失败时Gtest将会打印出assertion时的源文件和出错行的位置,以及附加的失败信息。这些输出的附加信息用户可以直接通过“<<”在这些断言宏后面。
Gtest中,断言的宏可以理解为分为两类,一类是ASSERT系列,一类是EXPECT系列。
ASSERT_* 系列的断言(Fatal assertion),当检查点失败时,退出当前函数(注意:并非退出当前案例)。
EXPECT_* 系列的断言(Nonfatal assertion),当检查点失败时,继续执行下一个检查点(每一个断言表示一个测试点)。
通常情况应该首选使用EXPECT_,因为ASSERT_*在报告完错误后不会进行清理工作,有可能导致内存泄露问题。
Fatal assertion |
Nonfatal assertion |
Verifies |
ASSERT_TRUE(condition); |
EXPECT_TRUE(condition); |
condition is true |
ASSERT_FALSE(condition); |
EXPECT_FALSE(condition); |
condition is false |
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++大部分原生类型都是可以使用二进制对比的。但是对于自定义类型,我们就要定义一些操作符的行为,比如=、<等。
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*
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 |
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对于浮点数的对比也是单独的
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 |
TEST宏是一个很重要的宏,它构成一个测试特例,它的原型是:
TEST宏的第一个参数是test_suite_name(测试套件名),第二个参数是test_name(测试特例名)。测试套件名和测试特例名(也叫测试名)的区别和联系:
测试套件(Test Case)是为某个特殊目标而编制的一组测试输入、执行条件以及预期结果,以便测试某个程序路径或核实是否满足某个特定需求,测试特例是测试套件下的一个(组)测试。
以上述代码为例,三段TEST宏构成的是一个测试套件——测试套件名是FactorialTest(阶乘方法检测,测试Factorial函数),该用例覆盖了三种测试特例——Negative、Zero和Positive——即检测输入参数是负数、零和正数这三种特例情况。
对于测试套件名和测试特例名,不能有下划线(_)。因为GTest源码中需要使用下划线把它们连接成一个独立的类名
这样也就要求,我们不能有相同的“测试套件名和特例名”的组合——否则类名重合。
测试套件名和测试特例名的分开,使得我们编写的测试代码有着更加清晰的结构——即有相关性也有独立性。相关性是通过相同的测试套件名联系的,而独立性通过不同的测试特例名体现的。
测试PositiveNum这个类。
使用TEST_F前需要创建一个固件类,继承于::testing::Test类。在类内部使用public或者protected描述其成员,为了保证实际执行的测试子类可以使用其成员变量。在构造函数或者继承于::testing::Test类中的SetUp方法中,可以实现我们需要构造的数据。在析构函数或者继承于::testing::Test类中的TearDown方法中,可以实现一些资源释放的代码。“构造函数/析构函数”和“SetUp/TearDown”的选择,对于什么时候选择哪对,没有统一的标准。一般来说就是构造/析构函数里忌讳做什么就不要在里面做,比如抛出异常等。
TEST_F宏和TEST宏的实现非常接近,只是TEST_F宏的封装更加开放一些,所以对TEST宏的功能多了一些扩展。他的原型是:
第一个参数为测试套件名(与创建的固件类名一致),第二个为测试名,可任意取。
第一个测试中,修改了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。
在设计测试案例时,经常需要考虑给被测函数传入不同的值的情况。我们之前的做法通常是写一个通用方法,然后编写在测试案例调用它。即使使用了通用方法,这样的工作也是有很多重复性的。
测试IsPrime这个函数(判断输入值是否为质数)。
用TEST这个宏,需要编写如下的测试案例,每输入一个值就需要写一个测试点,这还只是在一个测试中,如果把每个测试点单独创建一个测试,工作量就更大。
使用TEST_P这个宏,对输入进行参数化,就简单很多。
创建一个参数化类IsPrimeParamTest。继承自TestWithParam这个模板类,TestWithParam又继承Test和WithParamInterface
WithParamInterface
使用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 提供了多种预处理事件机制,非常方便我们在测试之前或之后做一些操作。
1. 全局的,所有测试执行前后。
2. TestSuite级别的,在某测试套件中第一个测试前,最后一个测试执行后。
3. TestCase级别的,每个测试前后。
要实现全局事件,必须写一个类,继承testing::Environment类,实现里面的SetUp和TearDown方法。
1. SetUp()方法在所有案例执行前执行
2. TearDown()方法在所有案例执行后执行
还需要在main函数中通过调用testing::AddGlobalTestEnvironment这个函数将事件挂进来,也就是说,我们可以写很多个这样的类,然后将他们的事件都挂上去,AddGlobalTestEnvironment这个函数要放在RUN_ALL_TEST之前。
需要写一个类,继承testing::Test,然后实现两个静态方法
1. SetUpTestCase() 方法在第一个TestCase之前执行
2. TearDownTestCase() 方法在最后一个TestCase之后执行
TestCase事件是挂在每个案例执行前后的,实现方式和Test'Suites的几乎一样,不过需要实现的是SetUp方法和TearDown方法:
1. SetUp()方法在每个TestCase之前执行
2. TearDown()方法在每个TestCase之后执行。
RUN_ALL_TESTS()这个宏,从名字上来看,就是运行所有的测试用例,这才是我们运行测试用例的真正入口。它的原型是:
RUN_ALL_TESTS()是一个宏,将其实现为函数,在这里,调用了UnitTest单例的Run函数,看调用过程,可以看到,依次调用的过程是
1.UnitTest::Run()
2. UnitTestImpl::RunAllTests()
3. TestCase::Run()
4. TestInfo::Run()
5. Test::Run()
【1】google mock 介绍
【2】google test 介绍