Google Test的初级应用
[TOC]
本文通过简单的自建项目总结C++测试框架,Google Test 的初级使用.
基础
概述
Google Test的项目主页在Github上:Github: Google Test.
这个项目中同时包含了GoogleTest和 GoogleMock 两个工具,这里先只关注第一个。
因为 GoogleMock 是 Mock 框架, 即模仿待测交互对象的一种技术框架. 在自动驾驶中不太适用,一般思路是直接接专业的汽车动力学模型软件(Carsim等),对于其他环境与传感器的交互, 通过采集数据回灌数据进行测试性价比会更高(结构化的场景可以使用仿真软件实现,例如CARLA).
支持的操作系统OS包含下面这些:
- Linux
- Mac OS X
- Windows
编译器Compilers:
- gcc 5.0+
- clang 5.0+
- MSVC 2015+
构建系统Build systems:
目前有很多的项目都使用了Google Test:
理念
Google Test认为自己开发的这套东西不止支持单元测试,理论上支持所有测试.优点为它坚持了如下原则:
- Tests should be independent and repeatable.每个case都是不同的object,保证每次测试结果一致.
- Tests should be well organized and reflect the structure of the tested code. 使test case容易被维护,因为与项目结构一致.
- Tests should be portable and reusable.多平台.
- When tests fail, they should provide as much information about the problem as possible.因此支持
EXPECT
模式,保证不会因为一个case失败而后面都不进行测试. - The testing framework should liberate test writers from housekeeping chores and let them focus on the test content.测试内容书写简洁.
- Tests should be fast.支持多线程/共享资源.
支持多线程,只能在支持pthreads的平台上.
部署与调用
官网出处:Generic Build Instructions.
部署(Ubuntu)
1 | git clone https://github.com/google/googletest.git -b release-1.11.0 |
上面默认也安装GoogleMock,排除GoogleMock如下.
1 | cmake .. -DBUILD_GMOCK=OFF |
要求支持C++11,可以通过
set(CMAKE_CXX_STANDARD 11)
显式约束.
通过调用库的方式(CMake)
find_package
或者 pkg_check_modules
指令.
例如find_package(GTest CONFIG REQUIRED)
.
查找成功后可以使用库:GTest::gtest
, GTest::gmock
.
与项目源码一起编译
通过CMake的 add_subdirectory()
指令与项目源码一起编译.好处是可以与项目源码保持一致的compiler and linker settings , 避免了 using incompatible libraries (eg debug/release) .具体方式如下:
- 下载GTEST源码后放入固定目录下,这是最不具有灵活性的方式,尤其对于持续集成系统而言.
- 把GTEST源码放到工程目录里,最简单但是很难保证GTEST能及时更新.
- 通过Git submodules等方式把GTEST 同步到项目中.
- 使用CMake的下载功能放入configure step. 除了考虑网络要求以外最合适的方式,例子如下.
1
2
3
4
5
6
7
8
9
10
11
12
13
14include(FetchContent)
FetchContent_Declare(
googletest
# 随时更新
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
# 链接到 gtest 或者 gtest_main 即可
add_executable(example example.cpp)
target_link_libraries(example gtest_main)
add_test(NAME example_test COMMAND example)要求CMake 3.14以上由于
FetchContent_MakeAvailable()
.
多线程测试
GTEST是线程安全的,基于pthread库.#include "gtest/gtest.h"
后,可以通过GTEST_IS_THREADSAFE
宏检测,有的话#defined
它为1
.
也可以通过-DGTEST_HAS_PTHREAD=1
编译选项显式约束pthread库的必要性,0
为非必要.
避免宏名冲突
由于C++中宏不支持namespace,GTEST里的宏有可能会与项目里的宏冲突,解决办法是在编译时加入前缀.例如宏名FOO
.
1 | -DGTEST_DONT_DEFINE_FOO=1 |
执行此编译选项后FOO
变为 GTEST_FOO
.
目前FOO
可以是 ASSERT_EQ
, ASSERT_FALSE
, ASSERT_GE
, ASSERT_GT
, ASSERT_LE
, ASSERT_LT
, ASSERT_NE
, ASSERT_TRUE
, EXPECT_FALSE
, EXPECT_TRUE
, FAIL
, SUCCEED
, TEST
, TEST_F
.
使用案例
下面为一个简单的演示项目.源码参见git.
项目文件
项目头文件内容如下,为一个操作接口类,分别实现加/乘/判断是否大于0的函数.
1 |
|
项目cpp文件如下:
1 |
|
CMakeLists文件
1 | cmake_minimum_required(VERSION 2.8.11) |
最终测试可执行文件链接的库如下:
- 待测试的软件库
-
gtest
库 -
gtest_main
库(测试cpp里可以不用写main函数了) -
pthread
库(多线程测试)
测试文件(test program)
编写测试cpp如下:
1 |
|
测试cpp文件解析:
test case(其实对应的ISTQB概念是 test suite,一个test suite包含多个test).
基本的测试语法如下:
1 | TEST(TestSuiteName, TestName) { |
每个TEST
为1个case(名称为TEST_demo_add
即后面两个参数的拼接),每个case里可能有多个判断.case之间需要保证没有前后依赖关系.
测试断言基础类型
由于Google Test对于结果分类为success, nonfatal failure, fatal failure,断言(判断)有两种形式:
-
ASSERT_*
:这类断言是Fatal的.一旦这个断言出错,则直接从测试函数中返回,不会再继续后面的测试. -
EXPECT_*
:这类断言是Nonfatal的.它的效果是,如果某个断言出错,则输出一个错误信息,但是接下来仍然会继续执行后面的测试.
下面的断言类型重载<<
,实现流式输出.
1 | ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length"; |
可以进行的断言方法主要有下面这些:
布尔断言
Fatal | Nonfatal | 说明 |
---|---|---|
ASSERT_TRUE(condition) |
EXPECT_TRUE(condition) |
断言 condition 为 true |
ASSERT_FALSE(condition) |
EXPECT_FALSE(condition) |
断言 condition 为 false |
二进制断言
Fatal | Nonfatal | 说明 |
---|---|---|
ASSERT_EQ(expected, actual) |
EXPECT_EQ(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 |
说明:
- EQ:EQual
- NE:Not Equal
- LT:Less Than
- LE:Less Equal
- GT:Greater Than
- GE:Greater Equal
比较值都可是指针,但是注意空指针比较:使用EXPECT_EQ(
ptr
, nullptr)
而不是EXPECT_EQ(
ptr
, NULL)
.
适用于字符串 std::string .但不适用于C string,如果传入的值为C string的话只比较内存地址不比较实际值.
字符串断言
Fatal | Nonfatal | 说明 |
---|---|---|
ASSERT_STREQ(expected, actual) |
EXPECT_STREQ(expected, actual) |
两个C string相同 |
ASSERT_STRNE(str1, str2) |
EXPECT_STRNE(str1, str2) |
两个C string不相同 |
ASSERT_STRCASEEQ(exp, act) |
EXPECT_STRCASEEQ(exp, act) |
忽略大小写,两个C string相同 |
ASSERT_STRCASENE(str1, str2) |
EXPECT_STRCASENE(str1, str2) |
忽略大小写,两个C string不相同 |
浮点数断言
包括FLOAT
与DOUBLE
.
Fatal | Nonfatal | 说明 |
---|---|---|
ASSERT_FLOAT_EQ(exp, act) |
EXPECT_FLOAT_EQ(exp, act) |
两个float数值相等 |
ASSERT_DOUBLE_EQ(exp, act) |
EXPECT_DOUBLE_EQ(exp, act) |
两个double数值相等 |
ASSERT_NEAR(val1, val2, abs_err) |
EXPECT_NEAR(val1, val2, abs_err) |
val1和val2的差距不超过abs_err |
测试结果:
1 | Running main() from .../gtest_main.cc |
出现了一个FAIL的case,The difference between d.multiply(1, 1) and 3 is 2, which exceeds 0.001
.1乘以1等于1与3之间的差值远大于容许误差0.001.
Test Fixture
如果仔细观察,可以发现demo d;
构造语句在每条测试用例中都出现了.如果对象构造起来成本比较高的话,很浪费资源,能实现资源共享吗?Test Fixture
可以做到.
要使用Test Fixture
,需要创建一个类继承自Google Test中的::testing::Test
(第一个::
之前为空).
资源共享必然设计到互相影响的问题(race condition),解决办法是重载::testing::Test
类中的Setup
和TearDown
两个函数分别来实现执行case前的设置与执行case后的清空操作.当然如果原本测试的case中不会修改测试类的状态的话,也不需要做什么. Google Test不会重复使用 Test Fixture
保证资源的安全性,测试的稳定性.
Note that different tests in the same test suite have different test fixture objects, and googletest always deletes a test fixture before it creates the next one. googletest does not reuse the same test fixture for multiple tests.
应用Test Fixture
例子变为如下,注意要把TEST
变更为TEST_F
:
1 |
|
test fixture class 中使用using
或者 typedef
简洁代码, 尤其方便区分死亡测试(后面介绍)与非死亡测试:
1 | class FooTest : public testing::Test { ... }; |
构造main函数测试
如果想在test program cpp文件里做一些处理,需要main函数接口方便的话,Google test也提供了相关的支持.
只需要把下面的语句放到上面的文件下面(所有test的定义),然后在CMakeLists中删除对gtest_main库的依赖即可.
1 | int main(int argc, char **argv) { |
其中RUN_ALL_TESTS
宏代表所有的test都已经run完毕(执行成功返回0
,失败返回1
.)
对于自动测试,使用
RUN_ALL_TESTS()
来判断测试成功与否.只能调用RUN_ALL_TESTS()
一次.
参考链接:
https://google.github.io/googletest/
https://paul.pub/gtest-and-coverage/
Google Test的初级应用