现代 C++ 实战(16):GoogleTest 单元测试
C++ 编译器能抓住类型错误,却抓不住「divide(10, 0) 该不该抛异常」「阶乘边界算对没有」——这些只能靠跑起来验证。第 15 篇 的工厂、策略可以测;本篇用 GoogleTest 把测试写进工程:TEST、Fixture、参数化、Mock、CMake 集成。
demo:ref/cpp_demo/basics/testing_demo/(C++17,GoogleTest v1.17.0)。
这是「现代 C++ 实战」系列的第 16 篇。建议先读 第 15 篇:现代设计模式。
一、为什么 C++ 更需要测试?
| 编译期能抓住 | 编译期抓不住 |
|---|---|
| 类型不匹配、未声明标识符 | 除零、越界、错误算法 |
| 部分 constexpr 逻辑 | 多线程竞态、资源泄漏 |
| Concepts 约束 | API 契约(返回值、异常) |
重构 第 14 篇 线程池、第 08 篇 错误处理时,回归测试是安全网。GoogleTest(gtest)+ GoogleMock(gmock)是 C++ 生态事实标准。
二、GoogleTest 基础:TEST 与断言
1 |
|
被测对象是 src/calculator.hpp 里的 Calculator:全是 static 成员函数,add / subtract / multiply / divide 接收并返回 double(divide 除数为 0 时抛 std::invalid_argument("除数不能为 0")),factorial(int) 返回 long long,is_prime(int) 返回 bool。
| 宏 | 含义 |
|---|---|
TEST(Suite, Name) |
独立测试用例,Suite 用于分组 |
EXPECT_* |
失败继续执行本用例后续断言 |
ASSERT_* |
失败立即终止本用例 |
1 | TEST(CalculatorTest, ExpectVsAssert) { |
想亲眼看到两者的差别,demo 另外准备了一个故意失败的用例,用 DISABLED_ 前缀默认禁用,不影响 ctest 通过:
1 | TEST(CalculatorTest, DISABLED_ExpectVsAssertFailureDemo) { |
1 | ./build/testing_demo --gtest_also_run_disabled_tests --gtest_filter='*FailureDemo*' |
输出里能看到两条失败信息:EXPECT_EQ 失败后后续断言照常执行;ASSERT_EQ 失败后本用例立即返回,第三条断言不再运行。
常用断言:
| 宏 | 用途 |
|---|---|
EXPECT_EQ / EXPECT_NE |
相等 / 不等 |
EXPECT_TRUE / EXPECT_FALSE |
布尔 |
EXPECT_LT / EXPECT_GT / … |
大小比较 |
EXPECT_NEAR |
浮点近似(第三参数为误差) |
EXPECT_DOUBLE_EQ |
浮点「按位」相等 |
EXPECT_THROW(expr, ExceptionType) |
期望抛指定类型异常 |
EXPECT_ANY_THROW(expr) |
期望抛任意异常 |
EXPECT_NO_THROW(expr) |
期望不抛 |
EXPECT_STREQ |
C 字符串相等(demo 用来比对 e.what()) |
入口(demo main.cpp):
1 | int main(int argc, char** argv) { |
也可链接 GTest::gtest_main,省去手写 main。demo 两者都用了:main.cpp 自带 main,同时链接了 GTest::gtest_main——链接器优先使用目标文件里的 main,静态库里的那份不会被拉进来,所以不冲突。
三、Test Fixture:共享 setup / teardown
多个用例要共用初始化时,用 TEST_F + 继承 ::testing::Test:
1 | class CalculatorFixture : public ::testing::Test { |
每个 TEST_F 都会:新建 Fixture → SetUp → 测试体 → TearDown。用例之间不共享 Fixture 实例,避免状态污染。
四、参数化测试:TEST_P
同一逻辑、多组输入,避免复制粘贴:
1 | class CalculatorParamTest |
demo 里还有一组同样写法的 PrimeParamTest(TestWithParam<std::tuple<int, bool>>,8 组输入,实例化名 PrimeValues)。
| 组件 | 作用 |
|---|---|
TestWithParam<T> |
参数类型 T |
TEST_P |
参数化用例 |
INSTANTIATE_TEST_SUITE_P |
绑定参数表;每组参数生成一个独立测试 |
还可 ::testing::Combine 做笛卡尔积;TYPED_TEST 对多种类型实例化同一模板测试——demo 用 ::testing::Types<int, long, long long, float, double> + TYPED_TEST_SUITE(TypedCalculatorTest, TestTypes),AddWorksWithDifferentTypes 对每种 TypeParam 各跑一次 add。
五、Mock 与依赖注入
单元测试要隔离被测单元:数据库、网络、文件系统应替换为假实现。
| 手段 | 做法 |
|---|---|
| 依赖注入 | 构造函数传入接口或 std::function |
| 接口 + 假对象 | 测试里传 FakeDatabase |
| GMock | MOCK_METHOD 生成 mock,设 EXPECT_CALL |
demo 只用到了 gmock 的 Matchers,没有 Mock 用例;下面是示意代码(需 #include <gmock/gmock.h> 并链接 GTest::gmock):
1 | class Database { |
原则:测逻辑不测基础设施;能注入就不 new 真实依赖。与 第 15 篇 Strategy / Factory 天然配合。
六、Matchers:EXPECT_THAT
GMock 提供 Matchers,断言更可读(demo 含 <gmock/gmock.h>):
1 | std::vector<int> primes = {2, 3, 5, 7, 11, 13}; |
适合容器、字符串、浮点区间;复杂条件用 AllOf / AnyOf / Not 组合(demo 的 CalculatorTest.MoreMatchers 还演示了 IsTrue() / IsFalse() / AnyOf)。
七、CMake 集成:FetchContent + CTest
demo CMakeLists.txt 用 FetchContent 拉 gtest v1.17.0,无需系统预装:
1 | include(FetchContent) |
| 步骤 | 说明 |
|---|---|
FetchContent_MakeAvailable |
下载并 add_subdirectory gtest |
enable_testing() |
启用 CTest |
add_test |
ctest / CI 可批量跑 |
BUILD_GMOCK ON |
同时构建 gmock,Matchers 需要 |
GTest::gtest_main |
提供默认 main(demo 自带 main.cpp,实际用的是自己的) |
运行:
1 | cd ref/cpp_demo/basics/testing_demo |
一共 29 个用例:CalculatorTest 8 个、CalculatorFixture 2 个、FactorialValues/CalculatorParamTest 6 个、PrimeValues/PrimeParamTest 8 个、TypedCalculatorTest 5 种类型各 1 个,全部 PASSED;末尾会提示 YOU HAVE 1 DISABLED TEST,就是上面那个故意失败的演示用例。
过滤用例:./testing_demo --gtest_filter=CalculatorTest.Add;运行被禁用的用例:加 --gtest_also_run_disabled_tests。
八、编写测试的习惯
| 建议 | 原因 |
|---|---|
| 一个 TEST 只测一件事 | 失败时定位快 |
| 命名说清意图 | DivideByZeroThrows 优于 Test3 |
| 优先 EXPECT,慎用 ASSERT | 一次看到多个失败 |
| 边界与异常 | 0、负数、空容器、除零 |
| CI 里跑 ctest | 每次 push 自动回归 |
九、小结
| 主题 | 要点 |
|---|---|
| TEST / EXPECT / ASSERT | 基本用例与断言语义 |
| TEST_F | SetUp/TearDown 共享夹具 |
| TEST_P | 多组参数,INSTANTIATE_TEST_SUITE_P |
| Mock | 依赖注入 + GMock 隔离外部 |
| CMake | FetchContent + enable_testing + add_test |
现代 C++ 实战系列第 16 篇完。下一篇 C++23 新特性——
expected、deducing this、std::print。
系列导航
| 篇号 | 标题 | 状态 |
|---|---|---|
| 15 | 现代设计模式 | ✅ |
| 16 | GoogleTest 单元测试(本篇) | ✅ |
| 17 | C++23 新特性 | ✅ |











