在 CMake 中使用 Google-Test
示例目录的源码树:
1 | $ tree |
测试通过:
1 | $ make test |
测试失败:
1 | $ make test |
具体的测试信息会保存在 build/Testing/Temporary/LastTest.log 文件
注1:googletest 也可以使用 clang 或 g++ 进行单独编译,编译命令为:
1 | $ clang++ UnitTest.cc -I ../google-test/googletest-src/googletest/include/ -L../google-test/googletest-build/googlemock/gtest -lgtest -lgtest_main -lpthread -std=c++11 -Wall |
注2:若测试时需要有配置文件,例如一个 ConfigUnittest.cc 会读取一个测试专用的配置文件:Config_unittest.ini 时,可以在对应目录下的 CMakeLists.txt 文件中添加
1 | # Config_unitest |
注3:若希望将拷贝形式变成执行 make copy_ini_file 的形式,则可以采用下面的 cmake 命令
1 | add_custom_target(copy_ini_file) |
3rd_party/google-test/CmakeLists.txt.in 文件
1 | cmake_minimum_required(VERSION 3.0) |
3rd_party/google-test/CmakeLists.txt 文件
1 | # Download and unpack googletest at configure time |
CMakeLists.txt 文件
1 | cmake_minimum_required(VERSION 3.5) |
CMakeLists.txt 编写总结:
最低版本要求 + 项目信息
将要测试的代码打包成待测库文件
执行 add_subdirectory 下载并构建 Google Test
打开测试开关
添加测试文件 unit_tests.cpp 为可执行目标 unit_tests
将 unit_tests 和待测库文件,以及 Google Test 的库文件链接到一起
执行 add_test 运行测试程序
unit_tests.cpp
1 |
|
googletest 的基本知识
注意:由于历史原因,googletest 中所提到的部分术语和常规软件工程中的术语不同,以下有一张对应表格
| googletest 中的术语 | 常规软件工程中所使用的术语 | 备注信息 |
|---|---|---|
| Test Case | Test Suited(测试套件) | 由一个或多个测试用例所构成的测试 |
| Test | Test Cas(测试用例) | 单个的测试代码 |
断言
- Fatal Assertion (ASSERT_*):一旦测试不通过,立即终止测试,并返回测试信息
- Nonfatal Assertion(EXCEPT_*):一旦测试不通过,报告测试信息,并接着执行下一个测试
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
ASSERT_TRUE(condition); |
EXPECT_TRUE(condition); |
condition is true |
ASSERT_FALSE(condition); |
EXPECT_FALSE(condition); |
condition is false |
ASSERT_EQ(val1, val2); |
EXPECT_EQ(val1, val2); |
val1 == val2 |
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 |
ASSERT_STREQ(str1,str2); |
EXPECT_STREQ(str1,str2); |
the two C strings have the same content |
ASSERT_STRNE(str1,str2); |
EXPECT_STRNE(str1,str2); |
the two C strings have different contents |
ASSERT_STRCASEEQ(str1,str2); |
EXPECT_STRCASEEQ(str1,str2); |
the two C strings have the same content, ignoring case |
ASSERT_STRCASENE(str1,str2); |
EXPECT_STRCASENE(str1,str2); |
the two C strings have different contents, ignoring case |
示例代码:
1 | // 可在测试断言后用 << 运算符打印自己的信息 |
注意事项:
若 val1 和 val2 是用户自定义类型,则必须重载相应的二元运算符以支持对应的比较操作。由于 Google C++ Style 规定禁止重载运算符,因此只能使用如 ASSERT_TRUE( val1 < val2) 这样的形式
在一般情况下更倾向于使用 ASSERT_EQ(actual, expected) ,而不是 ASSERT_TRUE(actual == expected)。这主要是因为当测试失败时,前者能够返回更多的信息(actual 和 expected 在实际测试中的值)
ASSERT_EQ() 用于判断两个指针是否相等时,只会判断这两个指针变量在内存中的位置是否相同,而并不会比较两个指针所指向的内容是否相同。要比较两个 C 风格的字符串字面值是否相等,应该采用 ASSERT_STREQ()。对于 string 对象则没有这个限制,依然可以使用 ASSERT_EQ
在判断一个 C 风格的指针是否为空或不为空时,使用 *_EQ(c_str, nullptr) 或者 *_NE(c_str, nullptr)。这是因为
nullptr是一种类型,而NULL不是。而且NULL指针和空的 string 对象是不同的。
Test 的创建
一个简单的示例:
1 | // Tests factorial of 0. |
在上述的例子中,使用了宏 Test(TestSuitName, TestName) 来定义和命名一个测试函数(无返回值)。TestSuitName 代表测试套件的名称,而 TestName 则代表单个测试的名称。由于测试结果会按照测试套件名称进行分组,因此逻辑上相关的测试最好集中到同一个测试套件当中。另外,不同的测试套件中的同名测试互不影响。TestSuitName 和 TestName 中不能带有 ‘_’ 字符
测试夹具(Test Fixture)【测试夹具可以为多个测试提供相同的数据配置】
一个简单的示例:
1 | //待测试的源文件 |
通过例子可以看出,使用测试夹具的基本流程如下:
- 从 ::testing::Test 派生一个测试夹具类(通常命名为 类名+Test)
- 在夹具类中定义稍后测试所需要使用的对象。注意测试夹具类中的成员均采用 protected 作为访问限定符,以便夹具类的子类也可以访问这些成员。
- 必要的情况下,编写构造函数或 SetUp 函数来进行夹具对象的初始化,并编写析构函数或 TearDown 函数来进行回收资源的工作
- 若有必要,为测试定义子例程
在执行上述测试代码的过程中,会发生以下情况:
- googletest 会创建一个 QueueTest 对象 t1
- 对 t1 执行 SetUp() 进行初始化
- 在 t1 上运行第一个测试 IsEmptyInitially
- 对 t1 执行 TearDown() 函数
- 创建一个 QueueTest 对象 t2
- 对 t2 对象执行 SetUp() 函数
- 在 t2 上运行第二个测试 DequeueWorks
- 对 t2 对象执行 TearDown() 函数
什么时候使用构造/析构函数,什么时候又应当使用 SetUp/TearDown 函数 ?
使用构造/析构函数的好处:
- 在创建 test fixture 对象的时候,可以将其设置为 const,从而避免意外修改到对象当中的数据
- 能够确保 test fixture 对象的派生对象在构造的时候优先构造基类子对象,析构的时候最后析构基类子对象
使用 SetUp / TearDwon 的优点:
C++ 在构造函数中无法使用多态。因此在需要使用多态进行构造的场合,你只能使用 SetUp 和 TearDown 函数
C++ 构造 / 析构函数的函数体中无法使用 ASSERT_* 断言
如果 TearDown 的操作可能会抛出异常,那么只能使用 TearDown 的方式
在 main 中调用测试
一个简单的示例:
1 |
|
在上述的例子当中,googletest 会自动将创建好的测试套件以及测试夹具中的测试注册起来,并通过 RUN_ALL_TESTS()来调用所有的测试。对于 RUN_ALL_TESTS() 有以下两点注意事项:
- 不要忽略
RUN_ALL_TESTS()的返回值, 0 代表通过, 1 则代表失败。对于 main 函数而言,如果没有返回RUN_ALL_TESTS()的返回值会导致编译错误 -
RUN_ALL_TESTS()只能调用一次。调用两次或以上的RUN_ALL_TESTS()和 googletest 的一些其他特性相冲突,因此并不被支持。 - 在调用
RUN_ALL_TESTS()之前需要调用::testing::InitGoogleTest(&argc, argv);以正确解析命令行参数。这可以使得用户能够通过命令行来控制测试程序的行为
googletest 提供了一个默认的 main 函数,如果你不需要在 main 函数中完成什么定制化的操作,则可以直接将测试程序连接到 gtest_main 当中即可。
- 本文作者: Phoenix
- 本文链接: https://phoenix500526.github.io/2020/10/03/googletest测试框架初探/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!