GoogleTest是Google开源的一个测试框架,使用这个框架我们可以很方便的对我们的项目进行测试;
本文讲述了GoogleTest的基本使用;
源代码:
C++中使用GoogleTest进行单元测试
安装并配置GoogleTest
得益于 vcpkg,我们可以非常简单的安装和配置GoogleTest库;
1 | vcpkg install gtest |
注:
- GoogleTest的名称为
gtest; - 你可能需要安装的是 x64的版本;
安装完成之后,根据提示,在我们的CMake项目中增加配置,并为我们的可执行文件添加链接库即可:
1 | add_executable(main_test main_test.cc) |
至此,配置完成;
关于如何配置 vcpkg 默认安装64位:
GoogleTest官方文档:
第一个GoogleTest例子
下面我们创建一个单测文件;
main_test.cc
1 |
|
代码首先引入了头文件 gtest/gtest.h,该头文件中包含了 GoogleTest 中核心的宏等;
随后我们使用了TEST()宏创建了一个单元测试用例;
TEST()宏的使用方法如下:
1 | TEST(test_suite_name, test_case_name) { |
第一个参数为整个测试组的名称,第二个参数为测试组中具体某个用例的名称;
最后在 main 函数中:
首先输出了单元测试的文件位置,随后使用启动测试时指定的参数初始化测试,最后调用RUN_ALL_TESTS();开启测试;
启动测试后结果如下:
1 | /Users/jasonkayzk/self-workspace/cpp-learn/cmake-build-debug/main_test |
执行成功!
断言
gtest 提供了大量的测试断言函数,大体上分为了两类:
ASSERT_*:执行失败,退出当前的测试函数立即返回(注意:并非退出当前案例);EXPECT_*:执行失败,并不会退出当前测试函数,继续向下执行;
下面给出了两个例子:
1 | TEST(ExpectAndAssert, ExpectTest) { |
执行后输出:
1 | [==========] Running 2 tests from 1 test suite. |
一些比较常用的断言有:
1、布尔值检查:
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
| ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition is true |
| ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition is false |
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 |
3、字符串比较:
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
| ASSERT_STREQ(expected_str, actual_str); | EXPECT_STREQ(expected_str, actual_str); | 两个C字符串有相同的内容 |
| ASSERT_STRNE(str1, str2); | EXPECT_STRNE(str1, str2); | 两个C字符串有不同的内容 |
| ASSERT_STRCASEEQ(expected_str, actual_str); | EXPECT_STRCASEEQ(expected_str, actual_str); | 两个C字符串有相同的内容,忽略大小写 |
| ASSERT_STRCASENE(str1, str2); | EXPECT_STRCASENE(str1, str2); | 两个C字符串有不同的内容,忽略大小写 |
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 |
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 |
对相近的两个数比较:
| 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 |
6、类型对比断言:
该类断言只有一个::testing::StaticAssertTypeEq<T, T>():
- 当类型相同时,它不会执行任何内容;
- 如果不同则会引起编译错误;
需要注意的是:要使代码触发编译器推导类型,否则也会发生编译错误;
如:
1 | template <typename T> class Foo { |
如下的代码就不会引起编译冲突:
1 | void Test1() { Foo<bool> foo; } |
但是下面的代码由于引发了编译器的类型推导,所以会触发编译错误:
1 | void Test2() { Foo<bool> foo; foo.Bar(); } |
7、几个特殊的断言:
SUCCEED()宏:直接标记断言成功;FAIL()宏:标记致命错误(同ASSERT_*);ADD_FAILURE()宏:标记非致命错误(同EXPECT_*);
更多断言见官方文档:
自定义错误信息
有的时候,我们可能会对默认情况下的错误的输出不满意:
例如:
1 | Failure |
此时,我们还可以使用 << 操作符来自定义输出信息;
1 | TEST(TestMessage, Message) { |
此时输出:
1 | Failure |
从输出中,我们可以很明显的看到,此时 result 为 4!
事件机制和TEST_F
使用过 JUnit 的小伙伴,应该对 @Before 和 @After 注解都不陌生;
他们允许我们在开始用例前、用例结束分别进行一些操作;
gtest 也提供了这样的事件,并且分为了多种类型:
- 全局事件;
- TestSuite事件;
- TestCase事件;
下面我们一一来看;
全局事件
要实现全局事件,必须写一个类来继承 testing::Environment 类,并实现里面的 SetUp 和 TearDown 方法;
此后:
SetUp()方法:在所有案例执行前执行;TearDown()方法:在所有案例执行后执行;
同时,还需要告诉 gtest 添加这个全局事件:
需要在main函数中通过 testing::AddGlobalTestEnvironment 方法将事件挂进来;
这也意味着,我们可以写很多个这样的类,然后将他们的事件都挂上去;
TestSuite事件
我们需要写一个类,继承 testing::Test,然后实现两个静态方法:
SetUpTestCase()方法:在第一个TestCase之前执行;TearDownTestCase()方法:在最后一个TestCase之后执行;
在编写测试案例时,我们需要使用 TEST_F 这个宏,第一个参数必须是我们上面类的名字,代表一个TestSuite;
TestCase事件
TestCase事件是挂在每个案例执行前后的,实现方式和上面的几乎一样,不过需要实现的是SetUp方法和TearDown方法:
SetUp()方法:在每个TestCase之前执行;TearDown()方法:在每个TestCase之后执行;
事件机制可以很好的帮助我们简化测试,例如:
我们可以使用事件机制来在测试函数之间共享数据;
下面提供了一个使用各种事件的例子:
1 | class GlobalEvent : public testing::Environment { |
首先,我们定义了 GlobalEvent 类,它继承了 testing::Environment,用于定义在整个测试开始之前、之后的操作;
随后,我们定义了 VectorTest 类,它继承了 testing::Test,用于定义在此测试组以及单个测试集合开始之前、之后的操作;
接着,我们使用 TEST_F 定义了两组个测试用例;
最后,我们在 main 函数中注册了我们之前定义的环境变量:::testing::AddGlobalTestEnvironment(new GlobalEvent);;
执行用例,我们得到下面的输出:
1 | Running main() from /Users/jasonkayzk/self-workspace/cpp-learn/main_test.cc |
需要注意的是:在上面的两个测试中:
1 | // Here we are using TEST_F, not TEST |
在一个测试函数中修改数据,并不会影响到其它测试函数;
这是因为,每个单独的测试用例都会单独调用我们重载过的 SetUp 和 TearDown 函数!
Appendix
参考文章:
- https://zhuanlan.zhihu.com/p/369466622
- http://senlinzhan.github.io/2017/10/08/gtest/
- https://www.cnblogs.com/coderzh/archive/2009/04/06/1430364.html
- https://www.cnblogs.com/helloworldcode/p/9606838.html
源代码: