gtest 快速入门
前言:之前在工作中简单用过gtest,但当时项目比较赶时间,对测试并不是很重视。经常出现一个人改完代码后,模块之间出现bug情况,然后花很长时间排查bug。吸取教训后准备在新项目中引入gtest,花了两天时间学习了gtest文档,发现它功能还是蛮多的。准备整理一个gtest系列的专栏,也是检验自己学习成果。
一、 gtest快速入门
1.1 gtest是什么
gtest 是 Google Test 的简称,是一个由 Google 开发与维护的 C++ 测试框架。它提供了丰富的断言和测试工具,帮助开发者编写更好的 C++ 测试代码。
特点:
- 丰富的断言:支持多种断言宏,如
EXPECT_TRUE、ASSERT_TRUE等,便于验证测试结果。 - 支持多种测试结构:例如
TEST、TEST_F和TEST_P等,以满足不同的测试场景。 - 自动生成测试报告:可指定生成xml、json格式测试报告
- 跨平台:支持 Windows、Linux、macOS 等多个平台。
- 与构建系统集成:可与 CMake、Bazel 等构建工具无缝集成。
1.2 构建一个gtest项目
我们可以直接从github上下载gtest作为一个独立项目来学习,也可将gtest合并到现有的项目中去。
将gtest作为独立项目
gtest本身提供了许多示例供我们学习,示例代码位于googletest/samples目录下
1 | git clone https://github.com/google/googletest.git # 下载gtest最新代码 |
整合到现有的项目中
修改现有项目的CMakeLists.txt文件,声明对gtest 的依赖关系。
1 | include(FetchContent) |
上述配置声明了对从 GitHub 下载的gtest依赖,1.10.0表示要下载的gtest版本,因为我系统上gcc编译器比较老,不支持c++ 14,所以我下的gtest版本较低,大家可以根据自身环境下载合适版本。
将 gtest 声明为依赖项后,便可以在自己的项目中使用gtest了。例如创建一个hello_test.cc的文件,测试计算阶乘的函数是否正确。
1 |
|
gtest提供了一些断言,您可以使用它们来测试代码的行为。上面的示例演示了一些基本断言,后面将会详细讲解这些断言的功能。
可以发现上述测试代码中没有main函数,那么怎么编译运行呢?我们只需要链接gtest-main库,它提供了一个默认的 main 函数实现,用于运行所有的测试。
要编译测试程序,请将以下内容添加到CMakeLists.txt:
1 | add_executable( |
现在,让我们运行测试程序看下效果吧,可以看到运行成功,所以断言都通过了。
1 | [user1@develop tests]$ ./hello_test |
1.3 gtest入门
1.3.1 基本概念
gtest使用断言(Assertion)来检查某些条件是否为真,以此验证代码的行为,可以认为断言就是一个检查点。**断言的结果有成功、 非致命失败和致命失败三种。**当断言失败时,会打印断言所处的文件名与行号,以便我们快速定位错误位置。
在gtest中,断言主要分为两类,ASSERT_*版本失败时会产生致命失败,并中止当前函数。EXPECT_*版本会产生非致命失败,不会中止当前函数。通常EXPECT_*是首选,因为它们允许在测试中报告多个错误。如果在某个断言失败后,后续测试代码继续运行没有任何意义则应使用ASSERT_*。
可以通过 << 运算符向断言中添加自定义错误信息,当断言失败时,这些信息会显示在测试输出中,帮助快速定位问题。任何可以流式传输到ostream的内容都可以流式传入到断言中——特别是C风格字符串和C++ string对象。
1 | ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length"; |
一个测试程序可以包含多个测试套件(test suite),一个测试套件包含一个或多个测试。应该将测试分组到能够反映测试代码结构的测试套件中。测试使用断言来验证测试代码的行为,如果测试崩溃或断言失败(不管是否为致命失败),则认为该测试失败;否则认为测试成功。
已知限制:
gtest的设计是线程安全的。在pthread库可用的系统上,实现是线程安全的。目前,在其他系统(例如Windows)上从两个线程并发地使用gtest断言是不安全的。在大多数测试中,这不是问题,因为断言通常在主线程中完成。如果您想提供帮助,您可以自愿为您的平台实现gtest-port.h中必要的同步原语。
1.3.2 TEST
TEST是一个宏,用于定义测试套件(Test Suite)相关的测试。 TEST宏有两个参数,第一个参数是测试套件的名称,第二个参数是测试名称,两个名称都必须是有效的 C++ 标识符,并且不应包含任何下划线字符。测试的全名由测试套件+测试名组成。来自不同测试套件的测试名可以相同。
要创建测试:
- 使用
TEST宏定义并命名测试函数。这是一个没有返回值的 C++ 函数。 - 在此函数中,除了您想要包含的任何有效 C++ 语句外,还使用
gtest提供的各种断言来检查条件是否满足。 - 测试结果由断言决定;如果测试中的任何断言失败(无论是致命的还是非致命的),或者测试崩溃,则整个测试失败。否则,测试成功。
1 | TEST(TestSuiteName, TestName) |
比如我们有一个计算阶乘的函数,此功能的测试套件可能如下所示:
1 |
|
gtest按测试套件对测试结果进行分组,因此逻辑上相关的测试应该在同一个测试套件中;换句话说,它们在TEST宏中第一个参数应该相同。在上面的例子中,我们有两个测试, HandlesZeroInput和HandlesPositiveInput,它们属于同一个测试FactorialTest。
运行结果如下:
1 | [user1@develop tests]$ ./hello_test |
1.3.3 TEST_F
TEST_F是一个宏,用于定义测试夹具(Test Fixture)相关的测试。它是 TEST 宏的扩展(_F代表“Fixture”),允许在同一测试夹具的多个测试之间共享相同的配置和资源。使用 TEST_F 可以避免重复代码,并确保测试环境的一致性。TEST宏有两个参数,第一个参数是测试夹具的名称,第二个参数是测试名称,两个名称都必须是有效的 C++ 标识符,并且不应包含任何下划线字符。
要创建夹具:
- 定义一个测试夹具类,它必须继承
testing::Test类 - 在夹具类内部,声明您计划使用的任何资源。
- 如果有必要,编写一个默认构造函数或override
SetUp函数初始化资源。 - 如果需要,请编写一个析构函数或override
TearDown函数来释放资源。要了解何时应使用构造函数/析构函数以及何时应使用SetUp()/TearDown(),请阅读常见问题解答。 - 如果需要,请定义要共享的测试子程序。
当使用夹具时,使用TEST_F()而不是TEST(),因为它允许您访问测试夹具中的对象和子例程:
1 | TEST_F(TestFixtureClassName, TestName) |
说明:对于使用TEST_F定义的每个测试,gtest将在运行时创建一个新的测试夹具对象,并调用SetUp函数初始化资源。运行完一个测试后,通过调用TearDown函数进行资源清理,然后删除这个对象。即同一测试夹具中的不同测试不会复用夹具对象,一个测试对夹具所做的任何更改都不会影响其他测试。
比如我们有个计算器类,要对它进行功能测试。我们希望每个测试都使用一些相同数据,并且不同测试之间互不影响,所以使用测试夹具,代码如下:
1 |
|
当运行测试程序时
gtest先创建一个CalculatorTest对象(我们称之为t1)- 调用
SetUp函数对计算器资源初始化,使用t1测试完加法功能后,调用TearDown清理资源,并删除t1对象 - 创建另一个对象重复上述步骤测试减法功能
不过上面示例可以不override SetUp和TearDown,使用构造与析构函数也能实现一样功能。
1 | [user1@develop tests]$ ./hello_test |
1.3.4 编写main函数
在上文的所有示例中我们都没有编写main函数,而是链接gtest-main库,库中提供了一个默认 main函数,它会自动初始化测试框架并运行所有的测试,这在绝大多数情况下适用。
如果需要在测试运行之前执行一些无法在测试套件和测试夹具内表达的自定义操作,我们需要自己编写main函数,这个main函数通常包含以下步骤:
- 测试运行前自定义操作
- 调用
::testing::InitGoogleTest函数解析gtest可以识别的命令行参数(标志),这允许用户通过各种参数控制测试程序的行为 - 调用
RUN_ALL_TESTS来运行所有的测试,如果所有测试运行成功返回0,否则返回1
当调用该RUN_ALL_TESTS:
- 保存所有
gtest标志的状态 - 为第一个测试创建一个测试夹具对象
- 调用
SetUp初始化 - 在夹具对象上运行测试
- 调用
TearDown清理 - 删除该对象
- 恢复所有
gtest标志的状态。 - 对下一个测试重复上述步骤,直到所有测试都运行完毕。
重要提示:您不能忽略
RUN_ALL_TESTS的返回值,否则将得到编译器错误。这样设计的理由是:自动测试服务应根据其退出码确定测试是否通过,而不是根据其stdout/stderr输出;另外,
RUN_ALL_TESTS只应调用一次。多次调用会与某些高级gtest功能(例如线程安全 死亡测试)相冲突。
假设我们有一个全局的日志系统,需要在运行测试之前初始化日志配置,并在所有测试结束后关闭日志系统。这时,我们需要自定义 main 函数来完成这些操作,例如创建一个hello_test.cc的文件,文件内容如下:
1 |
|
要编译测试程序,请将以下内容添加到CMakeLists.txt:
1 | add_executable( |
1.3.5 断言参考
gtest使用断言来验证代码行为,要使用断言,添加#include <gtest/gtest.h>。本节将列出gtest提供的所有断言,以后有需要可作参考。
gtest提供的断言宏大多数是EXPECT_*和ASSER_*配对出现。当失败时,EXPECT_*宏会产生非致命失败并允许当前函数继续运行,而ASSERT_* 宏会产生致命失败并中止当前函数。
1.3.5.1 布尔
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
ASSERT_TRUE(condition); |
EXPECT_TRUE(condition); |
condition is true |
ASSERT_FALSE(condition); |
EXPECT_FALSE(condition); |
condition is false |
1.3.5.2 整数、指针、string
以下断言比较两个值。值参数必须能被断言的比较运算符比较,否则编译器会报错。
如果参数支持<<运算符,则在断言失败时将调用它来打印参数。否则,GoogleTest 将尝试以最佳方式打印它们 - 请参阅 教 GoogleTest 如何打印您的值。
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
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_EQ、EXPECT_EQ和ASSERT_NE、EXPECT_NE还可以比较:
- C++
std::string - 指针,当比较指针是否为空应使用
EXPECT_EQ(ptr, nullptr)而不是EXPECT_EQ(ptr, NULL),对于ASSERT_NE也适用
1.3.5.3 C 风格字符串
以下断言比较两个C 风格字符串。要比较两个std::string 对象或 C风格字符串与空指针比较,请改用EXPECT_EQ或EXPECT_NE
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
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 content |
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 content, ignoring case |
1.3.5.4 浮点
由于舍入误差,两个浮点值完全匹配的可能性很小,因此EXPECT_EQ不合适。通常,为了使浮点比较有意义,用户需要仔细选择误差界限。GoogleTest 还提供了使用基于最后单位 (ULP) 的默认错误界限的断言。要了解有关 ULP 的更多信息,请参阅文章 比较浮点数。
彼此之间的差异在 4 个 ULP 以内则认为相等。
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
ASSERT_FLOAT_EQ(val1,val2); |
EXPECT_FLOAT_EQ(val1,val2); |
the two float values are almost equal |
ASSERT_DOUBLE_EQ(val1,val2); |
EXPECT_DOUBLE_EQ(val1,val2); |
the two double values are almost equa |
验证val1和val2之间的差值不会超过绝对误差界限abs_error。
| 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 |
1.3.5.5 异常
以下断言验证一段代码是否抛出异常。使用时需要在构建环境中启用异常。
| 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 |
测试例子:
1 |
|
测试结果:
1 | [user1@develop tests]$ ./hello_test |
1.3.5.6 谓词
以下断言可以验证更复杂的谓词(Predicate,一个返回bool的可调用对象),并打印比EXPECT_TRUE更清晰的失败信息。
参数pred是一个可调用对象,它接受与相应宏相同数量的参数。如果pred对给定的参数返回true,则断言成功,否则断言失败。当断言失败时,它打印出每个实参的值,便于排错,它最多接受5个参数。
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
ASSERT_PRED1(pred, val1); |
EXPECT_PRED1(pred, val1); |
pred(val1) return true |
ASSERT_PRED2(pred, val1, val2); |
EXPECT_PRED2(pred, val1, val2); |
pred(val1, val2) return true |
ASSERT_PRED3(pred, val1, val2, val3); |
EXPECT_PRED3(pred, val1, val2, val3); |
pred(val1, val2, val3) return true |
ASSERT_PRED4(pred, val1, val2, val3, val4); |
EXPECT_PRED2(pred, val1, val2, val3 ,val4); |
pred(val1, val2, val3 ,val4) return true |
ASSERT_PRED5(pred, val1, val2, val3, val4 ,val5); |
EXPECT_PRED2(pred, val1, val2, val3, val4, val5); |
pred(val1, val2, val3, val4, val5) return true |
示例:
1 |
|
运行结果:ASSERT_PRED2可以打印所有参数的值,有助于快速定位问题。
1 | [user1@develop tests]$ ./hello_test |
注意,如果给定的谓词是重载函数或函数模板,断言宏可能无法确定调用哪个版本,并且可能需要明确指定函数的类型。
对于IsPositive重载为单个参数为int或double版本,使用时需要显式指定函数的类型
1 | bool IsPositive(int n) |
对于函数模版也是一样的。
1 | template <typename T> |
如果您想自定义断言失败的错误信息,可以使用下面这类宏。参数pred_formatter是一个具有以下签名的可调用对象,其中val1、val2、 …valn是谓词参数的值,而expr1、expr2、 …exprn是源代码中对应的表达式。类型T1、T2、 …Tn 可以是值类型或引用类型,例T、T&、const T&等。有关返回类型的更多信息testing::AssertionResult,请参阅 返回 AssertionResult 的函数。
1 | testing::AssertionResult PredicateFormatter(const char* expr1, |
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
ASSERT_PRED_FORMAT1(pred_formatter, val1); |
EXPECT_PRED_FORMAT1(pred_formatter, val1); |
pred_formatter(expr1, val1) return true |
ASSERT_PRED_FORMAT2(pred_formatter, val1, val2); |
EXPECT_PRED_FORMAT2(pred_formatter, val1, val2); |
… |
ASSERT_PRED_FORMAT3(pred_formatter, val1, val2, val3); |
EXPECT_PRED_FORMAT3(pred_formatter, val1, val2, val3); |
… |
ASSERT_PRED_FORMAT4(pred_formatter, val1, val2, val3, val4); |
EXPECT_PRED_FORMAT4(pred_formatter, val1, val2, val3 ,val4); |
… |
ASSERT_PRED_FORMAT5(pred_formatter, val1, val2, val3, val4 ,val5); |
EXPECT_PRED_FORMAT5(pred_formatter, val1, val2, val3, val4, val5); |
… |
示例:
1 |
|
运行结果:
1 | [user1@develop tests]$ ./hello_test |
1.3.5.7 显式的成功和失败
本节中的断言直接生成成功或失败,而不是测试值或表达式。当由控制流而不是布尔表达式决定测试的成功或失败时,这很有用。
SUCCEED() : 生成成功,这并不会使整个测试成功。只有当测试在执行过程中没有任何断言失败时,测试才被视为成功。该SUCCEED断言纯粹是记录性的,目前不会生成任何用户可见的输出。但是,我们可能会在未来将SUCCEED消息添加到gtest输出中。
FAIL() :生成致命失败并从当前函数返回。只能在返回 void 的函数中使用。 有关详细信息,请参阅断言放置。
ADD_FAILURE():生成非致命失败,允许当前函数继续运行。
ADD_FAILURE_AT(file_path, line_number):在指定的文件和行号处生成非致命失败
示例:
1 |
|
1.3.5.8 匹配器
以下断言使用匹配器对测试值进行更灵活、可读性更强的验证。可以使用内置或自定义的匹配器来验证值是否与匹配器匹配。使用时需要链接gmock库。
EXPECT_THAT(value, matcher);ASSERT_THAT(value, matcher);
value:要测试的值。
matcher:匹配器,可以是内置的匹配器(如 Eq(), Le(), Gt() 等),也可以是自定义的匹配器。
例如,以下代码验证val是否介于 5 和 10 之间,字符串str是否以”Hello”开头。
1 |
|
1.3.5.9 Windows HRESULT
以下断言测试HRESULT成功或失败。
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
ASSERT_HRESULT_SUCCEEDED(expression); |
EXPECT_HRESULT_SUCCEEDED(expression); |
expression is a success HRESULT |
ASSERT_HRESULT_FAILED(expression); |
EXPECT_HRESULT_FAILED(expression); |
expression is a failure HRESULT |
1.3.5.10 死亡断言
死亡断言与后续进阶主题中死亡测试息息相关,因此将它们融为一节,将在后续进阶主题中讲解。
参考
GoogleTest User’s Guide