Google Test的初级应用

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认为自己开发的这套东西不止支持单元测试,理论上支持所有测试.优点为它坚持了如下原则:

  1. Tests should be independent and repeatable.每个case都是不同的object,保证每次测试结果一致.
  2. Tests should be well organized and reflect the structure of the tested code. 使test case容易被维护,因为与项目结构一致.
  3. Tests should be portable and reusable.多平台.
  4. When tests fail, they should provide as much information about the problem as possible.因此支持EXPECT模式,保证不会因为一个case失败而后面都不进行测试.
  5. The testing framework should liberate test writers from housekeeping chores and let them focus on the test content.测试内容书写简洁.
  6. Tests should be fast.支持多线程/共享资源.

    支持多线程,只能在支持pthreads的平台上.

部署与调用

官网出处:Generic Build Instructions.

部署(Ubuntu)

1
2
3
4
5
6
7
git clone https://github.com/google/googletest.git -b release-1.11.0
cd googletest # 主目录
mkdir build # 构建目录
cd build
cmake ..
make
sudo make install # 默认安装到/usr/local/

上面默认也安装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::gtestGTest::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
    14
    include(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_EQASSERT_FALSEASSERT_GEASSERT_GTASSERT_LEASSERT_LTASSERT_NEASSERT_TRUEEXPECT_FALSEEXPECT_TRUEFAILSUCCEEDTESTTEST_F.

使用案例

下面为一个简单的演示项目.源码参见git.

项目文件

项目头文件内容如下,为一个操作接口类,分别实现加/乘/判断是否大于0的函数.

1
2
3
4
5
6
7
8
9
#pragma once
#include <string>
class demo{
public:
int add(int a, int b) { return a + b; };
float multiply(float a, float b) { return a * b; };
bool greaterThan0(float a) { a > 0 ? true : false; };
std::string greaterThan0String(float a);
};

项目cpp文件如下:

1
2
3
4
5
6
#include "demo.h"
std::string demo::greaterThan0String(float a){
std::string t = "True";
std::string f = "False";
return a > 0 ? t : f;
}

CMakeLists文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmake_minimum_required(VERSION 2.8.11)
project(demo)

set(CMAKE_CXX_STANDARD 11)

set(GTEST googletest-release-1.8.1)
#通过上述把GTEST放入项目代码库中的形式
include_directories("./include" "${GTEST}/googletest/include/")
link_directories("build/gtest/googlemock/gtest/")

#把cpp编译成库
add_library(${CMAKE_PROJECT_NAME}_lib src/demo.cpp)

#测试cpp编译成可执行文件,以便运行进行测试
add_executable(unit_test test/unit_test.cpp)
target_link_libraries(unit_test ${CMAKE_PROJECT_NAME}_lib gtest gtest_main pthread)

最终测试可执行文件链接的库如下:

  • 待测试的软件库
  • gtest
  • gtest_main库(测试cpp里可以不用写main函数了)
  • pthread库(多线程测试)

测试文件(test program)

编写测试cpp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "demo.h"
#include "gtest/gtest.h"
using namespace std;
TEST(demo, add){
demo d;
EXPECT_EQ(d.add(1, 1), 2);
EXPECT_LT(d.add(1, 1), 3);
ASSERT_EQ(d.add(1, 1), 2);
}
TEST(demo, multiply){
demo d;
ASSERT_FLOAT_EQ(d.multiply(1, 1), 1);
EXPECT_NEAR(d.multiply(1, 1), 3, 0.001);
EXPECT_FLOAT_EQ(d.multiply(1, 1), 1);
}
TEST(demo, greaterThan0){
demo d;
EXPECT_TRUE(d.greaterThan0(1));
ASSERT_TRUE(d.greaterThan0(0.3));
}
TEST(demo, greaterThan0String){
demo d;
EXPECT_STREQ("True", d.greaterThan0String(1).c_str());
}

测试cpp文件解析:
test case(其实对应的ISTQB概念是 test suite,一个test suite包含多个test).
基本的测试语法如下:

1
2
3
TEST(TestSuiteName, TestName) {
... test body ...
}

每个TEST为1个case(名称为TEST_demo_add即后面两个参数的拼接),每个case里可能有多个判断.case之间需要保证没有前后依赖关系.

测试断言基础类型

由于Google Test对于结果分类为successnonfatal failurefatal 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不相同

浮点数断言

包括FLOATDOUBLE.

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Running main() from .../gtest_main.cc
[==========] Running 4 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 4 tests from demo
[ RUN ] demo.add
[ OK ] demo.add (0 ms)
[ RUN ] demo.multiply
/demo_simple/test/unit_test.cpp:19: Failure
The difference between d.multiply(1, 1) and 3 is 2, which exceeds 0.001, where
d.multiply(1, 1) evaluates to 1,
3 evaluates to 3, and
0.001 evaluates to 0.001.
[ FAILED ] demo.multiply (0 ms)
[ RUN ] demo.greaterThan0
[ OK ] demo.greaterThan0 (0 ms)
[ RUN ] demo.greaterThan0String
[ OK ] demo.greaterThan0String (0 ms)
[----------] 4 tests from demo (0 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 1 test case ran. (0 ms total)
[ PASSED ] 3 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] demo.multiply

1 FAILED TEST

出现了一个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类中的SetupTearDown两个函数分别来实现执行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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "demo.h"
#include "gtest/gtest.h"
using namespace std;
class demoTest : public ::testing::Test{
//保证子类都可以访问
protected:
//override显式重载
void SetUp() override
{
cout << "SetUp runs before each case." << endl;
}
//override显式重载
void TearDown() override
{
cout << "TearDown runs after each case." << endl;
}
demo d;
};

TEST_F(demoTest, add){
EXPECT_EQ(d.add(1, 1), 2);
EXPECT_LT(d.add(1, 1), 3);
ASSERT_EQ(d.add(1, 1), 2);
}
TEST_F(demoTest, multiply){
demo d;
ASSERT_FLOAT_EQ(d.multiply(1, 1), 1);
EXPECT_NEAR(d.multiply(1, 1), 3, 0.001);
EXPECT_FLOAT_EQ(d.multiply(1, 1), 1);
}
TEST_F(demoTest, greaterThan0){
demo d;
EXPECT_TRUE(d.greaterThan0(1));
ASSERT_TRUE(d.greaterThan0(0.3));
}
TEST_F(demoTest, greaterThan0String){
demo d;
EXPECT_STREQ("True", d.greaterThan0String(1).c_str());
}

test fixture class 中使用using 或者 typedef简洁代码, 尤其方便区分死亡测试(后面介绍)与非死亡测试:

1
2
3
4
5
6
7
8
9
10
11
class FooTest : public testing::Test { ... };

using FooDeathTest = FooTest;

TEST_F(FooTest, DoesThis) {
// normal test
}

TEST_F(FooDeathTest, DoesThat) {
// death test
}

构造main函数测试

如果想在test program cpp文件里做一些处理,需要main函数接口方便的话,Google test也提供了相关的支持.
只需要把下面的语句放到上面的文件下面(所有test的定义),然后在CMakeLists中删除对gtest_main库的依赖即可.

1
2
3
4
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

其中RUN_ALL_TESTS宏代表所有的test都已经run完毕(执行成功返回0,失败返回1.)

对于自动测试,使用RUN_ALL_TESTS()来判断测试成功与否.只能调用RUN_ALL_TESTS()一次.

参考链接:
https://google.github.io/googletest/
https://paul.pub/gtest-and-coverage/

作者

cx

发布于

2021-12-09

更新于

2022-07-16

许可协议