核心目标:掌握 Google Test(gtest)与 Google Mock(gmock)框架的核心用法,学会编写高质量的 C++ 单元测试,建立稳定的测试驱动开发(TDD)工作流,并能将 gtest 集成到 CMake + CI/CD 流水线中。
前置知识:熟悉 C++14/17 基础语法;了解 CMake 基本用法(
add_executable、target_link_libraries)。适用版本:Google Test v1.14.x(本指南以 v1.14.0 为基准)
1. 单元测试与 Google Test 概述
1.1 为什么选择 Google Test
Google Test(gtest)是 C++ 生态中使用最广泛的单元测试框架,被 LLVM、Chromium、TensorFlow、OpenCV 等重量级项目采用。
#mermaid-svg-aPEPOqiJYIIhh2fB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aPEPOqiJYIIhh2fB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aPEPOqiJYIIhh2fB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aPEPOqiJYIIhh2fB .error-icon{fill:#552222;}#mermaid-svg-aPEPOqiJYIIhh2fB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aPEPOqiJYIIhh2fB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aPEPOqiJYIIhh2fB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aPEPOqiJYIIhh2fB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aPEPOqiJYIIhh2fB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aPEPOqiJYIIhh2fB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aPEPOqiJYIIhh2fB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aPEPOqiJYIIhh2fB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aPEPOqiJYIIhh2fB .marker.cross{stroke:#333333;}#mermaid-svg-aPEPOqiJYIIhh2fB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aPEPOqiJYIIhh2fB p{margin:0;}#mermaid-svg-aPEPOqiJYIIhh2fB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aPEPOqiJYIIhh2fB .cluster-label text{fill:#333;}#mermaid-svg-aPEPOqiJYIIhh2fB .cluster-label span{color:#333;}#mermaid-svg-aPEPOqiJYIIhh2fB .cluster-label span p{background-color:transparent;}#mermaid-svg-aPEPOqiJYIIhh2fB .label text,#mermaid-svg-aPEPOqiJYIIhh2fB span{fill:#333;color:#333;}#mermaid-svg-aPEPOqiJYIIhh2fB .node rect,#mermaid-svg-aPEPOqiJYIIhh2fB .node circle,#mermaid-svg-aPEPOqiJYIIhh2fB .node ellipse,#mermaid-svg-aPEPOqiJYIIhh2fB .node polygon,#mermaid-svg-aPEPOqiJYIIhh2fB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aPEPOqiJYIIhh2fB .rough-node .label text,#mermaid-svg-aPEPOqiJYIIhh2fB .node .label text,#mermaid-svg-aPEPOqiJYIIhh2fB .image-shape .label,#mermaid-svg-aPEPOqiJYIIhh2fB .icon-shape .label{text-anchor:middle;}#mermaid-svg-aPEPOqiJYIIhh2fB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aPEPOqiJYIIhh2fB .rough-node .label,#mermaid-svg-aPEPOqiJYIIhh2fB .node .label,#mermaid-svg-aPEPOqiJYIIhh2fB .image-shape .label,#mermaid-svg-aPEPOqiJYIIhh2fB .icon-shape .label{text-align:center;}#mermaid-svg-aPEPOqiJYIIhh2fB .node.clickable{cursor:pointer;}#mermaid-svg-aPEPOqiJYIIhh2fB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aPEPOqiJYIIhh2fB .arrowheadPath{fill:#333333;}#mermaid-svg-aPEPOqiJYIIhh2fB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aPEPOqiJYIIhh2fB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aPEPOqiJYIIhh2fB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aPEPOqiJYIIhh2fB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aPEPOqiJYIIhh2fB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aPEPOqiJYIIhh2fB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aPEPOqiJYIIhh2fB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aPEPOqiJYIIhh2fB .cluster text{fill:#333;}#mermaid-svg-aPEPOqiJYIIhh2fB .cluster span{color:#333;}#mermaid-svg-aPEPOqiJYIIhh2fB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aPEPOqiJYIIhh2fB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aPEPOqiJYIIhh2fB rect.text{fill:none;stroke-width:0;}#mermaid-svg-aPEPOqiJYIIhh2fB .icon-shape,#mermaid-svg-aPEPOqiJYIIhh2fB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aPEPOqiJYIIhh2fB .icon-shape p,#mermaid-svg-aPEPOqiJYIIhh2fB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aPEPOqiJYIIhh2fB .icon-shape .label rect,#mermaid-svg-aPEPOqiJYIIhh2fB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aPEPOqiJYIIhh2fB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aPEPOqiJYIIhh2fB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aPEPOqiJYIIhh2fB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} EXPECT_TRUE / ASSERT_EQ
失败
被测代码
(函数/类)
gtest 测试框架
TEST / TEST_F / TEST_P
断言判定
✅ 通过
❌ 显示 预期 vs 实际
- 调用栈 + 源码位置
Google Test 核心优势:
| 优势 | 说明 |
|---|---|
| 工业级成熟度 | 由 Google 维护,历经十余年大规模验证 |
| xUnit 经典架构 | TEST / TEST_F / TEST_P 三级测试组织,学习曲线平滑 |
| Google Mock 原生集成 | gmock 与 gtest 深度耦合,无缝 Mock |
| 丰富的断言体系 | ASSERT_* / EXPECT_* 双轨系统,覆盖数值/字符串/异常/浮点 |
| 死亡测试 | EXPECT_DEATH 验证程序崩溃/退出行为 |
| 参数化测试 | TEST_P + INSTANTIATE_TEST_SUITE_P 组合数据驱动 |
| XML/JSON 报告 | 原生支持 CI 集成(Jenkins/GitLab/GitHub Actions) |
| 跨平台 | Linux / Windows / macOS / Android / iOS / QNX |
1.2 gtest 与 Catch2 对比速览
| 对比维度 | Google Test | Catch2 |
|---|---|---|
| 架构风格 | xUnit(TEST_F / SetUp / TearDown) | BDD 原生(SCENARIO / GIVEN / WHEN) |
| 头文件方式 | ❌ 需编译链接 | ✅ 支持 header-only |
| 断言风格 | EXPECT_EQ(a, b) |
REQUIRE(a == b) |
| Mock 框架 | ✅ gmock 原生内置 | ❌ 需第三方(FakeIt 等) |
| 死亡测试 | ✅ EXPECT_DEATH |
❌ 不原生支持 |
| 参数化测试 | ✅ TEST_P |
✅ GENERATOR / TEMPLATE_TEST_CASE |
| 编译速度 | ⭐⭐ | ⭐⭐⭐ |
| 社区生态 | 最大(LLVM/Chromium 等) | 活跃但较小 |
| 学习曲线 | 平缓(概念清晰) | 略陡(表达式模板) |
| 适合场景 | 企业级/大型项目/需要 Mock | 独立库/快速原型/BDD 爱好者 |
选择建议:如果你的项目需要 Mock 外部依赖、死亡测试或企业级 CI 集成,优先选 Google Test。如果你偏好 BDD 风格和 header-only 的极简依赖,选 Catch2。
2. 安装与工程配置
2.1 CMake FetchContent 集成(推荐)
cmake
cmake_minimum_required(VERSION 3.20)
project(MyProject LANGUAGES CXX)
# ═══════════════════════════════════════
# Google Test (仅在测试时拉取)
# ═══════════════════════════════════════
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
# 避免将 gtest 的测试和示例一起编译
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
set(BUILD_GMOCK ON CACHE BOOL "" FORCE)
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
if(BUILD_TESTING)
FetchContent_MakeAvailable(googletest)
include(CTest)
enable_testing()
endif()
# ═══════════════════════════════════════
# 主库
# ═══════════════════════════════════════
add_library(my_lib STATIC
src/math.cpp
src/string_util.cpp
)
target_include_directories(my_lib PUBLIC include)
# ═══════════════════════════════════════
# 测试目标
# ═══════════════════════════════════════
if(BUILD_TESTING)
add_executable(my_tests
tests/test_math.cpp
tests/test_string.cpp
)
target_link_libraries(my_tests PRIVATE
my_lib
GTest::gtest_main # 自带 main()
GTest::gmock_main # gmock 自带 main()
)
# 注册到 CTest
add_test(NAME my_tests COMMAND my_tests)
endif()
提示 :链接
GTest::gtest_main会自动提供main()函数,无需自己编写。
2.2 vcpkg 包管理集成
bash
# 安装 gtest(含 gmock)
vcpkg install gtest
# 或指定平台
vcpkg install gtest:x64-linux
cmake
# CMakeLists.txt 中只需 find_package
find_package(GTest CONFIG REQUIRED)
add_executable(my_tests tests/test_math.cpp)
target_link_libraries(my_tests PRIVATE my_lib GTest::gtest GTest::gtest_main)
使用 vcpkg toolchain 构建:
bash
cmake -B build -S . \
-DCMAKE_TOOLCHAIN_FILE=[vcpkg-root]/scripts/buildsystems/vcpkg.cmake \
-DBUILD_TESTING=ON
cmake --build build
2.3 系统包管理器安装
bash
# Ubuntu/Debian
sudo apt install libgtest-dev libgmock-dev
# macOS (Homebrew)
brew install googletest
# Fedora
sudo dnf install gtest gtest-devel gmock gmock-devel
2.4 项目目录结构建议
my_project/
├── include/
│ └── my_lib/
│ ├── math.h
│ └── string_util.h
├── src/
│ ├── math.cpp
│ └── string_util.cpp
├── tests/
│ ├── CMakeLists.txt
│ ├── unit/
│ │ ├── test_math.cpp
│ │ └── test_string_util.cpp
│ ├── integration/
│ │ └── test_database.cpp
│ ├── mocks/
│ │ └── mock_http_client.h
│ └── fixtures/
│ └── temp_dir_fixture.h
├── CMakeLists.txt
└── README.md
3. 第一个测试用例
3.1 最小测试程序
cpp
// tests/test_math.cpp
#include <gtest/gtest.h>
// 被测函数
int Add(int a, int b) {
return a + b;
}
// TEST(TestSuiteName, TestName)
TEST(MathTest, AddWorksForPositiveNumbers) {
EXPECT_EQ(Add(1, 2), 3);
EXPECT_EQ(Add(100, 200), 300);
}
TEST(MathTest, AddWorksForNegativeNumbers) {
EXPECT_EQ(Add(-1, -1), -2);
EXPECT_EQ(Add(-5, 3), -2);
}
TEST(MathTest, AddWorksForZero) {
EXPECT_EQ(Add(0, 0), 0);
EXPECT_EQ(Add(0, 42), 42);
}
编译运行:
bash
cmake -B build -S . -DBUILD_TESTING=ON
cmake --build build
cd build && ctest --output-on-failure
# 或直接运行
./my_tests
成功输出:
[==========] Running 3 tests from 1 test suite.
[----------] 3 tests from MathTest
[ RUN ] MathTest.AddWorksForPositiveNumbers
[ OK ] MathTest.AddWorksForPositiveNumbers (0 ms)
[ RUN ] MathTest.AddWorksForNegativeNumbers
[ OK ] MathTest.AddWorksForNegativeNumbers (0 ms)
[ RUN ] MathTest.AddWorksForZero
[ OK ] MathTest.AddWorksForZero (0 ms)
[----------] 3 tests from MathTest (0 ms total)
[==========] 3 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 3 tests.
3.2 断言宏体系
Google Test 提供两套断言宏:ASSERT_ * 和 EXPECT_*。
#mermaid-svg-OKcq4H5tdv9YFCyd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OKcq4H5tdv9YFCyd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OKcq4H5tdv9YFCyd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OKcq4H5tdv9YFCyd .error-icon{fill:#552222;}#mermaid-svg-OKcq4H5tdv9YFCyd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OKcq4H5tdv9YFCyd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OKcq4H5tdv9YFCyd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OKcq4H5tdv9YFCyd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OKcq4H5tdv9YFCyd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OKcq4H5tdv9YFCyd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OKcq4H5tdv9YFCyd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OKcq4H5tdv9YFCyd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OKcq4H5tdv9YFCyd .marker.cross{stroke:#333333;}#mermaid-svg-OKcq4H5tdv9YFCyd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OKcq4H5tdv9YFCyd p{margin:0;}#mermaid-svg-OKcq4H5tdv9YFCyd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OKcq4H5tdv9YFCyd .cluster-label text{fill:#333;}#mermaid-svg-OKcq4H5tdv9YFCyd .cluster-label span{color:#333;}#mermaid-svg-OKcq4H5tdv9YFCyd .cluster-label span p{background-color:transparent;}#mermaid-svg-OKcq4H5tdv9YFCyd .label text,#mermaid-svg-OKcq4H5tdv9YFCyd span{fill:#333;color:#333;}#mermaid-svg-OKcq4H5tdv9YFCyd .node rect,#mermaid-svg-OKcq4H5tdv9YFCyd .node circle,#mermaid-svg-OKcq4H5tdv9YFCyd .node ellipse,#mermaid-svg-OKcq4H5tdv9YFCyd .node polygon,#mermaid-svg-OKcq4H5tdv9YFCyd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OKcq4H5tdv9YFCyd .rough-node .label text,#mermaid-svg-OKcq4H5tdv9YFCyd .node .label text,#mermaid-svg-OKcq4H5tdv9YFCyd .image-shape .label,#mermaid-svg-OKcq4H5tdv9YFCyd .icon-shape .label{text-anchor:middle;}#mermaid-svg-OKcq4H5tdv9YFCyd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OKcq4H5tdv9YFCyd .rough-node .label,#mermaid-svg-OKcq4H5tdv9YFCyd .node .label,#mermaid-svg-OKcq4H5tdv9YFCyd .image-shape .label,#mermaid-svg-OKcq4H5tdv9YFCyd .icon-shape .label{text-align:center;}#mermaid-svg-OKcq4H5tdv9YFCyd .node.clickable{cursor:pointer;}#mermaid-svg-OKcq4H5tdv9YFCyd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OKcq4H5tdv9YFCyd .arrowheadPath{fill:#333333;}#mermaid-svg-OKcq4H5tdv9YFCyd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OKcq4H5tdv9YFCyd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OKcq4H5tdv9YFCyd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OKcq4H5tdv9YFCyd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OKcq4H5tdv9YFCyd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OKcq4H5tdv9YFCyd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OKcq4H5tdv9YFCyd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OKcq4H5tdv9YFCyd .cluster text{fill:#333;}#mermaid-svg-OKcq4H5tdv9YFCyd .cluster span{color:#333;}#mermaid-svg-OKcq4H5tdv9YFCyd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OKcq4H5tdv9YFCyd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OKcq4H5tdv9YFCyd rect.text{fill:none;stroke-width:0;}#mermaid-svg-OKcq4H5tdv9YFCyd .icon-shape,#mermaid-svg-OKcq4H5tdv9YFCyd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OKcq4H5tdv9YFCyd .icon-shape p,#mermaid-svg-OKcq4H5tdv9YFCyd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OKcq4H5tdv9YFCyd .icon-shape .label rect,#mermaid-svg-OKcq4H5tdv9YFCyd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OKcq4H5tdv9YFCyd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OKcq4H5tdv9YFCyd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OKcq4H5tdv9YFCyd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ASSERT_*
EXPECT_*
测试执行中遇到断言
使用哪个宏?
❌ 失败 → 立即退出当前测试函数
(后续断言不执行)
❌ 失败 → 记录失败,继续执行
(可收集多个失败信息)
测试函数结束
执行下一个断言
| 宏 | 行为 | 使用场景 |
|---|---|---|
ASSERT_TRUE(cond) |
断言为真,失败立即退出 | 前置条件(后续依赖) |
ASSERT_FALSE(cond) |
断言为假,失败立即退出 | 同上 |
EXPECT_TRUE(cond) |
断言为真,失败继续 | 可收集多个失败 |
EXPECT_FALSE(cond) |
断言为假,失败继续 | 同上 |
常用比较断言:
| 断言 | 含义 |
|---|---|
EXPECT_EQ(a, b) |
a == b |
EXPECT_NE(a, b) |
a != b |
EXPECT_LT(a, b) |
a < b |
EXPECT_LE(a, b) |
a <= b |
EXPECT_GT(a, b) |
a > b |
EXPECT_GE(a, b) |
a >= b |
cpp
// 示例:EXPECT_* vs ASSERT_*
TEST(AssertDemo, ExpectVsAssert) {
int a = 1, b = 2, c = 3;
EXPECT_EQ(a + b, 3); // ✅ 通过
EXPECT_EQ(a + c, 5); // ❌ 失败,继续往下执行
EXPECT_EQ(b + c, 5); // ✅ 通过(仍会执行)
ASSERT_EQ(a, 1); // ✅ 通过
ASSERT_EQ(a, 99); // ❌ 失败,测试立即终止
EXPECT_EQ(b, 2); // 这行不会被执行
}
3.3 断言失败信息解读
当断言失败时,gtest 会输出精确的失败信息:
test_math.cpp:15: Failure
Expected equality of these values:
Add(1, 2)
Which is: 3
5
添加自定义失败信息 (使用 << 操作符):
cpp
TEST(MathTest, AddWithCustomMessage) {
int a = 1, b = 2;
EXPECT_EQ(Add(a, b), 5)
<< "Add(" << a << ", " << b << ") should be 5, but got "
<< Add(a, b);
}
输出:
test_math.cpp:20: Failure
Expected equality of these values:
Add(1, 2)
Which is: 3
5
Add(1, 2) should be 5, but got 3
4. 测试夹具(Test Fixtures)
4.1 基本夹具:TEST_F
当多个测试需要共享相同的初始化逻辑时,使用 TEST_F(Fixture Test):
cpp
#include <gtest/gtest.h>
#include <vector>
#include <algorithm>
// 定义夹具类:每个 TEST_F 运行时都会创建独立实例
class VectorTest : public ::testing::Test {
protected:
void SetUp() override {
// 每个 TEST_F 开始前执行
v = {3, 1, 4, 1, 5, 9, 2, 6};
}
void TearDown() override {
// 每个 TEST_F 结束后执行(清理资源)
v.clear();
}
// 夹具成员(每个测试可独立使用)
std::vector<int> v;
};
TEST_F(VectorTest, SortWorks) {
std::sort(v.begin(), v.end());
EXPECT_EQ(v, std::vector<int>({1, 1, 2, 3, 4, 5, 6, 9}));
}
TEST_F(VectorTest, SizeIsCorrect) {
EXPECT_EQ(v.size(), 8);
}
TEST_F(VectorTest, ElementAccess) {
EXPECT_EQ(v[0], 3);
EXPECT_EQ(v[7], 6);
}
4.2 SetUp / TearDown 生命周期
TEST_F SizeIsCorrect TEST_F SortWorks VectorTest 夹具 测试运行器 TEST_F SizeIsCorrect TEST_F SortWorks VectorTest 夹具 测试运行器 #mermaid-svg-SkcfVYanhqIEqive{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SkcfVYanhqIEqive .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SkcfVYanhqIEqive .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SkcfVYanhqIEqive .error-icon{fill:#552222;}#mermaid-svg-SkcfVYanhqIEqive .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SkcfVYanhqIEqive .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SkcfVYanhqIEqive .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SkcfVYanhqIEqive .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SkcfVYanhqIEqive .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SkcfVYanhqIEqive .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SkcfVYanhqIEqive .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SkcfVYanhqIEqive .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SkcfVYanhqIEqive .marker.cross{stroke:#333333;}#mermaid-svg-SkcfVYanhqIEqive svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SkcfVYanhqIEqive p{margin:0;}#mermaid-svg-SkcfVYanhqIEqive .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SkcfVYanhqIEqive text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-SkcfVYanhqIEqive .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-SkcfVYanhqIEqive .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-SkcfVYanhqIEqive .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-SkcfVYanhqIEqive .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-SkcfVYanhqIEqive #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-SkcfVYanhqIEqive .sequenceNumber{fill:white;}#mermaid-svg-SkcfVYanhqIEqive #sequencenumber{fill:#333;}#mermaid-svg-SkcfVYanhqIEqive #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-SkcfVYanhqIEqive .messageText{fill:#333;stroke:none;}#mermaid-svg-SkcfVYanhqIEqive .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SkcfVYanhqIEqive .labelText,#mermaid-svg-SkcfVYanhqIEqive .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-SkcfVYanhqIEqive .loopText,#mermaid-svg-SkcfVYanhqIEqive .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-SkcfVYanhqIEqive .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-SkcfVYanhqIEqive .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-SkcfVYanhqIEqive .noteText,#mermaid-svg-SkcfVYanhqIEqive .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-SkcfVYanhqIEqive .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SkcfVYanhqIEqive .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SkcfVYanhqIEqive .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SkcfVYanhqIEqive .actorPopupMenu{position:absolute;}#mermaid-svg-SkcfVYanhqIEqive .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-SkcfVYanhqIEqive .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SkcfVYanhqIEqive .actor-man circle,#mermaid-svg-SkcfVYanhqIEqive line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-SkcfVYanhqIEqive :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 开始运行测试套件 new VectorTest() SetUp() 执行 TEST_F(SortWorks) TearDown() delete 实例 new VectorTest() ← 全新实例! SetUp() 执行 TEST_F(SizeIsCorrect) TearDown() delete 实例
关键规则 :每个
TEST_F独立拥有夹具实例。SetUp → 测试体 → TearDown 是一个原子周期。
4.3 静态夹具:SetUpTestSuite / TearDownTestSuite
当初始化代价高昂(如数据库连接)且可跨测试共享时:
cpp
class DatabaseTest : public ::testing::Test {
protected:
// ★ 整个 Test Suite 仅执行一次
static void SetUpTestSuite() {
// 建立数据库连接(共享)
db_connection = ConnectToDatabase("localhost:3306");
CreateTestTable();
}
static void TearDownTestSuite() {
DropTestTable();
DisconnectDatabase(db_connection);
}
// 每个测试前执行
void SetUp() override {
ClearTestTable(); // 每个测试干净的表
}
static DatabaseConnection* db_connection;
};
// 静态成员定义
DatabaseConnection* DatabaseTest::db_connection = nullptr;
TEST_F(DatabaseTest, InsertSingleRow) {
InsertRow(1, "hello");
EXPECT_EQ(QueryCount(), 1);
}
TEST_F(DatabaseTest, InsertMultipleRows) {
InsertRow(1, "a");
InsertRow(2, "b");
EXPECT_EQ(QueryCount(), 2); // ✅ 独立:仅 2 行
}
TEST_F InsertMultiple TEST_F InsertSingle 静态层 测试运行器 TEST_F InsertMultiple TEST_F InsertSingle 静态层 测试运行器 #mermaid-svg-BX4OKHguBTfhPG3x{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BX4OKHguBTfhPG3x .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BX4OKHguBTfhPG3x .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BX4OKHguBTfhPG3x .error-icon{fill:#552222;}#mermaid-svg-BX4OKHguBTfhPG3x .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BX4OKHguBTfhPG3x .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BX4OKHguBTfhPG3x .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BX4OKHguBTfhPG3x .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BX4OKHguBTfhPG3x .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BX4OKHguBTfhPG3x .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BX4OKHguBTfhPG3x .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BX4OKHguBTfhPG3x .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BX4OKHguBTfhPG3x .marker.cross{stroke:#333333;}#mermaid-svg-BX4OKHguBTfhPG3x svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BX4OKHguBTfhPG3x p{margin:0;}#mermaid-svg-BX4OKHguBTfhPG3x .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BX4OKHguBTfhPG3x text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-BX4OKHguBTfhPG3x .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BX4OKHguBTfhPG3x .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-BX4OKHguBTfhPG3x .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-BX4OKHguBTfhPG3x .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-BX4OKHguBTfhPG3x #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-BX4OKHguBTfhPG3x .sequenceNumber{fill:white;}#mermaid-svg-BX4OKHguBTfhPG3x #sequencenumber{fill:#333;}#mermaid-svg-BX4OKHguBTfhPG3x #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-BX4OKHguBTfhPG3x .messageText{fill:#333;stroke:none;}#mermaid-svg-BX4OKHguBTfhPG3x .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BX4OKHguBTfhPG3x .labelText,#mermaid-svg-BX4OKHguBTfhPG3x .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-BX4OKHguBTfhPG3x .loopText,#mermaid-svg-BX4OKHguBTfhPG3x .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-BX4OKHguBTfhPG3x .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BX4OKHguBTfhPG3x .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-BX4OKHguBTfhPG3x .noteText,#mermaid-svg-BX4OKHguBTfhPG3x .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-BX4OKHguBTfhPG3x .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BX4OKHguBTfhPG3x .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BX4OKHguBTfhPG3x .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BX4OKHguBTfhPG3x .actorPopupMenu{position:absolute;}#mermaid-svg-BX4OKHguBTfhPG3x .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-BX4OKHguBTfhPG3x .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BX4OKHguBTfhPG3x .actor-man circle,#mermaid-svg-BX4OKHguBTfhPG3x line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-BX4OKHguBTfhPG3x :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 整个 Suite 开始 SetUpTestSuite() ← 仅一次 SetUp() → 测试 → TearDown() SetUp() → 测试 → TearDown() TearDownTestSuite() ← 仅一次
4.4 夹具继承与复用
cpp
// 基夹具:提供通用工具函数
class FileTestBase : public ::testing::Test {
protected:
void SetUp() override {
tmp_dir_ = std::filesystem::temp_directory_path() / "gtest_XXXXXX";
mkdtemp(tmp_dir_.data());
}
void TearDown() override {
std::filesystem::remove_all(tmp_dir_);
}
std::string tmp_dir_;
};
// 派生夹具:增加特定行为
class JsonFileTest : public FileTestBase {
protected:
void SetUp() override {
FileTestBase::SetUp(); // ★ 调用基类 SetUp
json_path_ = tmp_dir_ + "/test.json";
}
std::string json_path_;
};
TEST_F(JsonFileTest, WriteAndRead) {
WriteJson(json_path_, R"({"key": "value"})");
auto data = ReadJson(json_path_);
EXPECT_EQ(data["key"], "value");
}
5. 测试组织与命名
5.1 Test Suite 分组策略
cpp
// 按模块分组
TEST(MathTest, ...)
TEST(StringUtilTest, ...)
TEST(ConfigParserTest, ...)
// 按测试类型分组
TEST(Database_Integration, ...) // 集成测试
TEST(Database_Deadlock, ...) // 并发测试
// 按功能特性分组
TEST(MathTest_Addition, ...)
TEST(MathTest_Subtraction, ...)
TEST(MathTest_EdgeCases, ...)
推荐的 Test Suite 命名层次:
<模块名> → MathTest
<模块名>_<子功能> → MathTest_Addition
<模块名>_<测试类型> → MathTest_EdgeCases
<模块名>_Integration → Database_Integration
<模块名>_Regression → Parser_Regression
5.2 测试命名规范
cpp
// ✅ 好的命名:描述行为 + 场景 + 期望结果
TEST(MathTest, AddReturnSumForTwoPositives)
TEST(MathTest, AddReturnZeroForNegativeAndPositiveSameMagnitude)
TEST(StringUtilTest, TrimRemovesLeadingWhitespace)
TEST(StringUtilTest, TrimReturnsEmptyStringForWhitespaceOnly)
TEST(ParserTest, ParseJsonThrowsOnMalformedBraces)
// ❌ 差的命名
TEST(MathTest, Test1) // 无语义
TEST(StringUtilTest, testTrim) // 风格不一致
TEST(ParserTest, BugFix1234) // 编号没有上下文
命名公式 :
[被测对象]_[操作/输入]_[期望行为/输出]
5.3 测试过滤运行
gtest 支持在命令行按模式过滤测试:
bash
# 运行名称匹配的测试
./my_tests --gtest_filter="MathTest.*"
./my_tests --gtest_filter="*Trim*"
./my_tests --gtest_filter="MathTest.Add*"
# 排除匹配的测试
./my_tests --gtest_filter="-*Slow*"
./my_tests --gtest_filter="-*Integration:*Slow*"
# 组合过滤(: 分隔正面和负面)
./my_tests --gtest_filter="MathTest.*:-*EdgeCase*"
# 列出所有测试(不运行)
./my_tests --gtest_list_tests
常用命令行选项:
| 选项 | 含义 |
|---|---|
--gtest_filter=PATTERN |
按模式过滤测试 |
--gtest_repeat=N |
重复运行 N 次(找 flaky test) |
--gtest_shuffle |
随机顺序运行 |
--gtest_random_seed=N |
指定随机种子 |
--gtest_print_time=1 |
显示耗时 |
--gtest_output=xml:report.xml |
输出 XML 报告 |
--gtest_output=json:report.json |
输出 JSON 报告 |
--gtest_break_on_failure |
首次失败时断点(调试) |
--gtest_throw_on_failure |
失败时抛异常(非致命测试) |
6. 高级断言与 Matcher
6.1 字符串断言
cpp
#include <gtest/gtest.h>
TEST(StringAssertions, Basic) {
std::string s = "Hello, Google Test!";
EXPECT_STREQ(s.c_str(), "Hello, Google Test!"); // C 字符串相等
EXPECT_STRNE(s.c_str(), "hello"); // C 字符串不等
EXPECT_STRCASEEQ(s.c_str(), "hello, google test!"); // 忽略大小写
}
TEST(StringAssertions, Contains) {
std::string s = "The quick brown fox";
// ★ 使用 gmock 的 HasSubstr Matcher
EXPECT_THAT(s, ::testing::HasSubstr("brown"));
EXPECT_THAT(s, ::testing::HasSubstr("fox"));
// HasSubstr 在字符数组上
EXPECT_THAT(s, ::testing::HasSubstr("quick"));
}
TEST(StringAssertions, StartsEndsWith) {
std::string s = "Hello, World!";
EXPECT_THAT(s, ::testing::StartsWith("Hello"));
EXPECT_THAT(s, ::testing::EndsWith("World!"));
// 否定匹配
EXPECT_THAT(s, ::testing::Not(::testing::StartsWith("Hi")));
}
TEST(StringAssertions, RegexMatch) {
std::string s = "Version 1.14.0";
EXPECT_THAT(s, ::testing::MatchesRegex("Version \\d+\\.\\d+\\.\\d+"));
EXPECT_THAT(s, ::testing::ContainsRegex("\\d+\\.\\d+\\.\\d+"));
}
6.2 浮点数断言
浮点数永远不要用 EXPECT_EQ:
cpp
#include <gtest/gtest.h>
#include <cmath>
TEST(FloatAssertions, NearAbsoluteError) {
double a = 0.1 + 0.2; // 实际 = 0.30000000000000004
// 绝对误差容限
EXPECT_NEAR(a, 0.3, 1e-10);
// 使用 DoubleNear Matcher
EXPECT_THAT(a, ::testing::DoubleNear(0.3, 1e-10));
}
TEST(FloatAssertions, NearRelativeError) {
// FloatNear: 相对误差容限
EXPECT_THAT(1000.001, ::testing::FloatNear(1000.0, 0.001));
}
TEST(FloatAssertions, NaN_Inf) {
double nan = std::nan("");
double inf = std::numeric_limits<double>::infinity();
EXPECT_TRUE(std::isnan(nan)); // 传统方式
EXPECT_THAT(nan, ::testing::Not(::testing::NanSensitiveDoubleEq(0.0)));
EXPECT_TRUE(std::isinf(inf));
}
| 断言 | 用途 |
|---|---|
EXPECT_NEAR(a, b, abs_error) |
绝对误差容限 |
FloatNear(b, rel_error) |
相对误差容限(float) |
DoubleNear(b, rel_error) |
相对误差容限(double) |
6.3 异常断言
cpp
#include <gtest/gtest.h>
#include <stdexcept>
int Divide(int a, int b) {
if (b == 0) throw std::invalid_argument("division by zero");
if (a < 0) throw std::runtime_error("negative numerator");
return a / b;
}
TEST(ExceptionTest, ThrowsSpecificException) {
EXPECT_THROW(Divide(1, 0), std::invalid_argument);
EXPECT_THROW(Divide(-1, 1), std::runtime_error);
}
TEST(ExceptionTest, DoesNotThrow) {
EXPECT_NO_THROW(Divide(10, 2));
// 同时检查返回值
EXPECT_EQ(Divide(10, 2), 5);
}
TEST(ExceptionTest, AnyException) {
EXPECT_ANY_THROW(Divide(1, 0));
EXPECT_ANY_THROW(Divide(-1, 1));
}
6.4 死亡测试(Death Test)
死亡测试用于验证代码是否会正确崩溃/退出:
cpp
#include <gtest/gtest.h>
void DieIfZero(int x) {
if (x == 0) {
std::cerr << "fatal: zero is not allowed" << std::endl;
std::abort();
}
}
void ExitWithMessage(int code) {
std::cerr << "exiting with code " << code << std::endl;
std::exit(code);
}
// ── 死亡测试必须用 ASSERT_DEATH / EXPECT_DEATH ──
TEST(DeathTest, AbortOnZero) {
EXPECT_DEATH(DieIfZero(0), "fatal: zero"); // 验证 stderr 消息
}
TEST(DeathTest, DoesNotDieOnNonZero) {
EXPECT_NO_FATAL_FAILURE(DieIfZero(42)); // 正常不应崩溃
}
TEST(DeathTest, ExitWithCode) {
// 验证退出码为正则匹配
EXPECT_EXIT(ExitWithMessage(42),
::testing::ExitedWithCode(42),
"exiting with code");
}
// ★ Linux 下死亡测试默认使用 fast 风格(fork+wait)
// 可在编译时或命令行修改为 threadsafe 风格
子进程 (死亡) 测试进程 子进程 (死亡) 测试进程 #mermaid-svg-oDtfrCC7MOrjPd52{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oDtfrCC7MOrjPd52 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oDtfrCC7MOrjPd52 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oDtfrCC7MOrjPd52 .error-icon{fill:#552222;}#mermaid-svg-oDtfrCC7MOrjPd52 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oDtfrCC7MOrjPd52 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oDtfrCC7MOrjPd52 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oDtfrCC7MOrjPd52 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oDtfrCC7MOrjPd52 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oDtfrCC7MOrjPd52 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oDtfrCC7MOrjPd52 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oDtfrCC7MOrjPd52 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oDtfrCC7MOrjPd52 .marker.cross{stroke:#333333;}#mermaid-svg-oDtfrCC7MOrjPd52 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oDtfrCC7MOrjPd52 p{margin:0;}#mermaid-svg-oDtfrCC7MOrjPd52 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oDtfrCC7MOrjPd52 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-oDtfrCC7MOrjPd52 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-oDtfrCC7MOrjPd52 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-oDtfrCC7MOrjPd52 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-oDtfrCC7MOrjPd52 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-oDtfrCC7MOrjPd52 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-oDtfrCC7MOrjPd52 .sequenceNumber{fill:white;}#mermaid-svg-oDtfrCC7MOrjPd52 #sequencenumber{fill:#333;}#mermaid-svg-oDtfrCC7MOrjPd52 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-oDtfrCC7MOrjPd52 .messageText{fill:#333;stroke:none;}#mermaid-svg-oDtfrCC7MOrjPd52 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oDtfrCC7MOrjPd52 .labelText,#mermaid-svg-oDtfrCC7MOrjPd52 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-oDtfrCC7MOrjPd52 .loopText,#mermaid-svg-oDtfrCC7MOrjPd52 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-oDtfrCC7MOrjPd52 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-oDtfrCC7MOrjPd52 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-oDtfrCC7MOrjPd52 .noteText,#mermaid-svg-oDtfrCC7MOrjPd52 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-oDtfrCC7MOrjPd52 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oDtfrCC7MOrjPd52 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oDtfrCC7MOrjPd52 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oDtfrCC7MOrjPd52 .actorPopupMenu{position:absolute;}#mermaid-svg-oDtfrCC7MOrjPd52 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-oDtfrCC7MOrjPd52 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oDtfrCC7MOrjPd52 .actor-man circle,#mermaid-svg-oDtfrCC7MOrjPd52 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-oDtfrCC7MOrjPd52 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} fork() 执行被测代码 abort() / exit() wait() 子进程 检查退出码 + stderr 匹配
6.5 gmock Matcher 速查
Google Mock 提供丰富的 Matcher,可与 EXPECT_THAT 配合:
cpp
using namespace ::testing;
// ── 容器匹配 ──
std::vector<int> v = {1, 2, 3, 4, 5};
EXPECT_THAT(v, Contains(3)); // 包含元素
EXPECT_THAT(v, Each(Gt(0))); // 每个元素 > 0
EXPECT_THAT(v, AllOf(Gt(0), SizeIs(5))); // 组合:> 0 且大小为 5
EXPECT_THAT(v, UnorderedElementsAre(5,4,3,2,1));// 无序相等
EXPECT_THAT(v, ElementsAre(1,2,3,4,5)); // 有序相等
EXPECT_THAT(v, IsEmpty()); // 为空
EXPECT_THAT(v, SizeIs(5)); // 大小为 5
// ── 指针匹配 ──
int* p = new int(42);
EXPECT_THAT(p, Pointee(42));
EXPECT_THAT(p, NotNull());
delete p;
EXPECT_THAT(nullptr, IsNull());
// ── 通用匹配 ──
EXPECT_THAT(42, AllOf(Gt(0), Lt(100))); // 0 < x < 100
EXPECT_THAT("hello", AnyOf(StartsWith("h"), StartsWith("H")));
EXPECT_THAT(3.14, DoubleNear(3.14, 1e-5));
EXPECT_THAT("abc", Not(HasSubstr("xyz")));
| Matcher 类别 | 示例 | 用途 |
|---|---|---|
| 比较 | Eq(v), Ne(v), Lt(v), Gt(v), Le(v), Ge(v) |
数值比较 |
| 布尔 | IsTrue(), IsFalse() |
布尔断言 |
| 浮点 | DoubleNear(v,e), FloatNear(v,e), NanSensitiveDoubleEq(v) |
浮点比较 |
| 字符串 | HasSubstr(s), StartsWith(s), EndsWith(s), MatchesRegex(p) |
字符串匹配 |
| 容器 | Contains(e), Each(m), ElementsAre(...), SizeIs(n) |
容器匹配 |
| 指针 | IsNull(), NotNull(), Pointee(m) |
指针匹配 |
| 组合 | AllOf(...), AnyOf(...), Not(m) |
逻辑组合 |
| 成员 | Field(&S::x, m), Property(&S::get_x, m) |
结构体/类成员 |
7. 参数化测试
7.1 TEST_P 与 INSTANTIATE_TEST_SUITE_P
参数化测试用于对同一测试逻辑在多个输入上验证:
cpp
#include <gtest/gtest.h>
// ① 定义参数化测试夹具
class MathParamTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {
};
// ② 定义测试逻辑:使用 GetParam() 获取参数
TEST_P(MathParamTest, MultiplyWorks) {
auto [a, b, expected] = GetParam();
EXPECT_EQ(a * b, expected)
<< "Failed: " << a << " * " << b << " != " << expected;
}
// ③ 实例化:注入数据
INSTANTIATE_TEST_SUITE_P(
Multiplication, // 前缀(分组名)
MathParamTest, // 夹具类名
::testing::Values( // 数据源
std::make_tuple(2, 3, 6),
std::make_tuple(0, 5, 0),
std::make_tuple(-2, 3, -6),
std::make_tuple(-2, -3, 6),
std::make_tuple(100, 0, 0)
)
);
输出示例:
[----------] 5 tests from Multiplication/MathParamTest
[ RUN ] Multiplication/MathParamTest.MultiplyWorks/0
[ OK ] ... (0 ms)
[ RUN ] Multiplication/MathParamTest.MultiplyWorks/1
...
[----------] 5 tests from Multiplication/MathParamTest (0 ms total)
7.2 组合参数生成
cpp
#include <gtest/gtest.h>
class CombineTest : public ::testing::TestWithParam<
std::tuple<int, std::string, bool>> {
};
TEST_P(CombineTest, CombinedLogic) {
auto [num, str, flag] = GetParam();
// 测试逻辑:num 和 flag 决定 str 的期望行为
if (flag) {
EXPECT_GT(num, 0);
EXPECT_FALSE(str.empty());
}
}
// ★ 使用 Combine: 笛卡尔积
INSTANTIATE_TEST_SUITE_P(
Cartesian,
CombineTest,
::testing::Combine(
::testing::Values(1, 2, 3), // num
::testing::Values("hello", "world"), // str
::testing::Bool() // flag
)
// 生成 3 × 2 × 2 = 12 个测试用例
);
数据源类型:
| 数据源 | 说明 | 示例 |
|---|---|---|
Values(v1, v2, ...) |
显式枚举 | Values(1, 2, 3) |
ValuesIn(container) |
从容器生成 | ValuesIn(std::vector{1,2,3}) |
ValuesIn(begin, end) |
从迭代器范围 | ValuesIn(arr, arr+3) |
Range(start, end) |
范围生成(不包含 end) | Range(0, 10) |
Range(start, end, step) |
带步进的范围 | Range(0, 100, 10) |
Bool() |
生成 false, true |
Bool() |
Combine(g1, g2, ...) |
笛卡尔积 | Combine(Values(1,2), Bool()) |
7.3 命名生成器
默认参数化测试名为 /0, /1, /2... 可读性差。用命名生成器改善:
cpp
// 自定义命名函数
class EdgeCaseTest : public ::testing::TestWithParam<int> {
};
INSTANTIATE_TEST_SUITE_P(
EdgeCases,
EdgeCaseTest,
::testing::Values(0, 1, -1, INT_MAX, INT_MIN),
[](const ::testing::TestParamInfo<int>& info) {
// ★ 返回自定义名称
switch (info.param) {
case 0: return "Zero";
case 1: return "One";
case -1: return "NegativeOne";
case INT_MAX: return "MaxInt";
case INT_MIN: return "MinInt";
default: return std::to_string(info.param);
}
}
);
输出:
[ RUN ] EdgeCases/EdgeCaseTest.TestEdgeCases/Zero
[ RUN ] EdgeCases/EdgeCaseTest.TestEdgeCases/MaxInt
[ RUN ] EdgeCases/EdgeCaseTest.TestEdgeCases/MinInt
8. Google Mock 基础
8.1 创建 Mock 类
Google Mock 通过宏自动生成 Mock 实现:
cpp
#include <gmock/gmock.h>
// 接口定义
class IHttpClient {
public:
virtual ~IHttpClient() = default;
virtual int Get(const std::string& url) = 0;
virtual int Post(const std::string& url, const std::string& body) = 0;
};
// ★ Mock 类定义
class MockHttpClient : public IHttpClient {
public:
// MOCK_METHOD(返回值类型, 方法名, (参数列表), (修饰符));
MOCK_METHOD(int, Get, (const std::string& url), (override));
MOCK_METHOD(int, Post,
(const std::string& url, const std::string& body),
(override));
};
MOCK_METHOD 宏签名(C++14+):
cpp
MOCK_METHOD(ReturnType, MethodName,
(ArgType1 arg1, ArgType2 arg2, ...), // 参数列表
(override | const | noexcept) // 修饰符
);
8.2 期望设置与验证
cpp
#include <gmock/gmock.h>
// 被测模块:依赖 IHttpClient
class ApiService {
IHttpClient& http_;
public:
explicit ApiService(IHttpClient& http) : http_(http) {}
bool FetchUserData(const std::string& userId) {
int status = http_.Get("/users/" + userId);
return status == 200;
}
};
TEST(ApiServiceTest, FetchUserDataReturnsTrueOn200) {
MockHttpClient mock;
// ★ 设置期望:Get 被调用 1 次,参数为 "/users/42",返回 200
EXPECT_CALL(mock, Get("/users/42"))
.Times(1)
.WillOnce(::testing::Return(200));
ApiService service(mock);
bool result = service.FetchUserData("42");
EXPECT_TRUE(result);
// ★ 测试结束时自动验证 EXPECT_CALL 的调用次数
}
期望设置核心 API:
| 方法 | 含义 |
|---|---|
.Times(n) |
期望调用 n 次 |
.WillOnce(action) |
设置第 1 次调用行为 |
.WillRepeatedly(action) |
设置后续调用行为 |
.RetiresOnSaturation() |
达到调用次数后"退休" |
调用次数控制:
cpp
using ::testing::AtLeast;
using ::testing::AtMost;
using ::testing::Between;
using ::testing::Exactly;
EXPECT_CALL(mock, Get(_))
.Times(AtLeast(1)); // 至少 1 次
EXPECT_CALL(mock, Get(_))
.Times(Exactly(3)); // 恰好 3 次
EXPECT_CALL(mock, Get(_))
.Times(Between(2, 5)); // 2~5 次
// 不限次数的快捷方式
EXPECT_CALL(mock, Get(_))
.WillRepeatedly(Return(200)); // 不限次数
8.3 ON_CALL 默认行为
ON_CALL 设置默认行为(无明确 EXPECT_CALL 时的 fallback):
cpp
TEST(ApiServiceTest, DefaultBehavior) {
MockHttpClient mock;
// ★ ON_CALL: 设置默认行为(不强制验证调用次数)
ON_CALL(mock, Get(::testing::_))
.WillByDefault(::testing::Return(404));
ON_CALL(mock, Post(::testing::_, ::testing::_))
.WillByDefault(::testing::Return(201));
// 具体的 EXPECT_CALL 覆盖默认行为
EXPECT_CALL(mock, Get("/users/42"))
.WillOnce(::testing::Return(200));
EXPECT_EQ(mock.Get("/users/42"), 200); // EXPECT_CALL 覆盖
EXPECT_EQ(mock.Get("/users/99"), 404); // 使用默认行为
}
8.4 参数匹配器
gmock 的 _ 是万能匹配器,还有更多精确匹配器:
cpp
using namespace ::testing;
// ── 通配符 ──
EXPECT_CALL(mock, Get(_)); // 任意参数
EXPECT_CALL(mock, Get(StrEq("/users/42"))); // 精确字符串
EXPECT_CALL(mock, Get(HasSubstr("users"))); // 包含子串
EXPECT_CALL(mock, Get(StartsWith("/"))); // 前缀
// ── 数值 ──
EXPECT_CALL(mock, Post("/data", _))
.WillOnce(Return(200));
EXPECT_CALL(calc, Compute(Gt(0), Lt(100))) // 0 < x < 100
.WillOnce(Return(42));
// ── 自定义 Matcher ──
MATCHER(IsEven, "is an even number") {
return arg % 2 == 0;
}
EXPECT_CALL(calc, Compute(IsEven(), _))
.WillOnce(Return(0));
gmock 内置动作:
| 动作 | 说明 |
|---|---|
Return(v) |
返回值 |
ReturnRef(v) |
返回引用 |
Throw(ex) |
抛出异常 |
Invoke(f) |
调用函数/仿函数 |
InvokeWithoutArgs(f) |
调用无参函数 |
DoDefault() |
执行默认行为 |
SetArgReferee<N>(v) |
设置引用参数值 |
SaveArg<N>(ptr) |
保存参数到指针 |
9. CMake 与 CTest 集成
9.1 多测试二进制组织
按模块拆分测试二进制,减少编译时间,支持并行:
cmake
# tests/CMakeLists.txt
# ═══════ 单元测试:数学模块 ═══════
add_executable(math_tests
unit/test_math.cpp
)
target_link_libraries(math_tests PRIVATE my_lib GTest::gtest_main)
add_test(NAME math_tests COMMAND math_tests)
# ═══════ 单元测试:字符串模块 ═══════
add_executable(string_tests
unit/test_string.cpp
unit/test_string_util.cpp
)
target_link_libraries(string_tests PRIVATE my_lib GTest::gtest_main)
add_test(NAME string_tests COMMAND string_tests)
# ═══════ 集成测试 ═══════
add_executable(integration_tests
integration/test_database.cpp
mocks/mock_db_connection.cpp
)
target_link_libraries(integration_tests PRIVATE
my_lib GTest::gtest_main GTest::gmock_main
)
add_test(NAME integration_tests COMMAND integration_tests
--gtest_filter=*Integration*)
9.2 CTest 集成
bash
# 运行所有 CTest 注册的测试
ctest --test-dir build --output-on-failure
# 并行运行(按 CPU 核心数)
ctest --test-dir build -j $(nproc)
# 按标签过滤
ctest --test-dir build -L unit
ctest --test-dir build -E integration
# 重复运行直到失败
ctest --test-dir build --repeat-until-fail 10
# 输出 XML 报告(Jenkins/GitLab 可解析)
ctest --test-dir build -T Test
cmake
# 设置 CTest 标签
set_tests_properties(math_tests
PROPERTIES LABELS "unit;math")
set_tests_properties(string_tests
PROPERTIES LABELS "unit;string")
set_tests_properties(integration_tests
PROPERTIES LABELS "integration;slow")
9.3 覆盖率收集
cmake
# CMakeLists.txt
if(ENABLE_COVERAGE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -fprofile-arcs -ftest-coverage")
target_link_options(my_lib PRIVATE --coverage)
endif()
bash
# 1. 构建并运行测试
cmake -B build -S . -DENABLE_COVERAGE=ON -DBUILD_TESTING=ON
cmake --build build
cd build && ctest --output-on-failure
# 2. 生成覆盖率报告
# 方式 A: gcovr (推荐)
gcovr -r .. --html --html-details -o coverage.html
# 方式 B: lcov
lcov --capture --directory . --output-file coverage.info
lcov --remove coverage.info '/usr/*' '*/googletest/*' -o coverage_filtered.info
genhtml coverage_filtered.info --output-directory coverage_report
# 3. 查看报告
open coverage_report/index.html
10. CI/CD 集成
10.1 GitHub Actions 模板
yaml
# .github/workflows/test.yml
name: Unit Tests
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
os: [ubuntu-22.04, windows-2022, macos-13]
build_type: [Debug, Release]
compiler:
- { cxx: g++-12, c: gcc-12 }
- { cxx: clang++-15, c: clang-15 }
exclude:
- os: windows-2022
compiler: { cxx: clang++-15, c: clang-15 }
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure CMake
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DBUILD_TESTING=ON \
-DENABLE_COVERAGE=${{ matrix.build_type == 'Debug' }}
- name: Build
run: cmake --build build --parallel $(nproc 2>/dev/null || echo 4)
- name: Test
run: ctest --test-dir build --output-on-failure -j $(nproc 2>/dev/null || echo 4)
- name: Generate Coverage (Debug only)
if: matrix.build_type == 'Debug' && runner.os == 'Linux'
run: |
cd build
gcovr -r .. --xml -o coverage.xml
gcovr -r .. --html --html-details -o coverage.html
- name: Upload Coverage
if: matrix.build_type == 'Debug' && runner.os == 'Linux'
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.os }}-${{ matrix.compiler.cxx }}
path: build/coverage.html
10.2 测试报告可视化
XML 报告输出:
cmake
# CMake 中设置默认输出 XML
add_test(NAME math_tests COMMAND math_tests
--gtest_output=xml:${CMAKE_BINARY_DIR}/test_reports/math_tests.xml)
JUnit 格式 CI 集成:
bash
# Jenkins Pipeline
stage('Test') {
steps {
sh 'ctest --test-dir build --output-on-failure -j 4'
junit 'build/**/test_reports/*.xml'
}
}
11. 最佳实践与常见模式
11.1 测试代码组织结构
tests/
├── unit/ # 纯单元测试(无外部依赖)
│ ├── test_math.cpp
│ ├── test_string.cpp
│ └── test_config.cpp
├── integration/ # 集成测试(真实 IO)
│ ├── test_database.cpp
│ └── test_network.cpp
├── regression/ # 回归测试
│ └── issue_042.cpp
├── mocks/ # Mock 定义
│ ├── mock_http_client.h
│ └── mock_filesystem.h
├── fixtures/ # 共享夹具
│ ├── temp_dir_fixture.h
│ └── db_connection_fixture.h
├── helpers/ # 测试工具函数
│ └── test_utils.h
├── CMakeLists.txt
└── main.cpp # 自定义 main(可选)
11.2 测试粒度和命名
cpp
// ✅ 一个 TEST 验证一个逻辑概念
TEST(StringUtilTest, TrimRemovesLeadingSpaces)
TEST(StringUtilTest, TrimRemovesTrailingSpaces)
TEST(StringUtilTest, TrimPreservesInternalSpaces)
TEST(StringUtilTest, TrimHandlesAllWhitespaceString)
TEST(StringUtilTest, TrimHandlesEmptyString)
// ✅ 使用 TEST_F 共享设置
TEST_F(VectorTest, PushBackIncreasesSize)
TEST_F(VectorTest, ClearEmptiesVector)
// ✅ 参数化测试消除重复
// 不好的做法:5 个几乎相同的 TEST
// 好的做法:1 个 TEST_P + INSTANTIATE_TEST_SUITE_P
命名公式回顾:
[被测模块]_[测试场景/输入]_[期望行为]
↓ ↓ ↓
StringUtil Trim RemovesLeadingSpaces
Config Load ThrowsOnMalformedFile
11.3 Mock vs Fake vs Stub 选择
| 类型 | 定义 | 何时使用 | gmock 实现 |
|---|---|---|---|
| Mock | 可编程设定行为,验证交互 | 需要验证调用次数/参数 | MOCK_METHOD + EXPECT_CALL |
| Fake | 轻量级替代实现 | 简单依赖,不需验证交互 | 手写 Fake 类 |
| Stub | 返回固定值 | 仅需要特定返回值 | ON_CALL + Return |
cpp
// Fake 示例:轻量内存存储替代真实数据库
class FakeUserRepository : public IUserRepository {
std::map<int, User> users_;
public:
User GetById(int id) override {
return users_.at(id); // 简单 map 实现
}
void Save(const User& u) override {
users_[u.id] = u;
}
};
// 当只需数据存储功能时用 Fake,无需 Mock 框架
TEST(UserServiceTest, GetExistingUser) {
FakeUserRepository repo;
repo.Save(User{42, "Alice"});
UserService service(repo);
EXPECT_EQ(service.GetUserName(42), "Alice");
}
11.4 应避免的陷阱
| 陷阱 | 说明 | 正确做法 |
|---|---|---|
| 测试顺序依赖 | Static 变量在测试间共享 | 每个 TEST 独立初始化 |
| 浮点直接 == | EXPECT_EQ(0.1+0.2, 0.3) 失败 |
用 EXPECT_NEAR / DoubleNear |
| 未清理全局状态 | Singleton 状态污染下一个测试 | SetUp/TearDown 中重置 |
| 硬编码时间/随机 | time(NULL) / rand() |
注入时钟/RNG 接口 |
| 过度 Mock | 所有依赖都 Mock | 简单依赖用手写 Fake |
| Mock 死了代码 | Mock 隐藏了真实 Bug | 必须有集成测试覆盖真实路径 |
| 忽略 ASSERT/EXPECT 选择 | 不当使用导致测试中断或漏报 | 前置条件用 ASSERT,数据收集用 EXPECT |
反面示例:
cpp
// ❌ 陷阱 1: 依赖测试执行顺序
static int global_counter = 0;
TEST(OrderTest, First) { global_counter = 42; }
TEST(OrderTest, Second) { EXPECT_EQ(global_counter, 42); } // 不可靠!
// ❌ 陷阱 2: 浮点比较用 EXPECT_EQ
TEST(FloatTrap, DirectComparison) {
EXPECT_EQ(0.1 + 0.2, 0.3); // 失败! 0.30000000000000004 ≠ 0.3
}
// ✅ 正确
TEST(FloatCorrect, UseNear) {
EXPECT_NEAR(0.1 + 0.2, 0.3, 1e-10); // 通过
}
11.5 测试驱动开发工作流
#mermaid-svg-KPy5OoiGFh7s3kSn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KPy5OoiGFh7s3kSn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KPy5OoiGFh7s3kSn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KPy5OoiGFh7s3kSn .error-icon{fill:#552222;}#mermaid-svg-KPy5OoiGFh7s3kSn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KPy5OoiGFh7s3kSn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KPy5OoiGFh7s3kSn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KPy5OoiGFh7s3kSn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KPy5OoiGFh7s3kSn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KPy5OoiGFh7s3kSn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KPy5OoiGFh7s3kSn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KPy5OoiGFh7s3kSn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KPy5OoiGFh7s3kSn .marker.cross{stroke:#333333;}#mermaid-svg-KPy5OoiGFh7s3kSn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KPy5OoiGFh7s3kSn p{margin:0;}#mermaid-svg-KPy5OoiGFh7s3kSn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KPy5OoiGFh7s3kSn .cluster-label text{fill:#333;}#mermaid-svg-KPy5OoiGFh7s3kSn .cluster-label span{color:#333;}#mermaid-svg-KPy5OoiGFh7s3kSn .cluster-label span p{background-color:transparent;}#mermaid-svg-KPy5OoiGFh7s3kSn .label text,#mermaid-svg-KPy5OoiGFh7s3kSn span{fill:#333;color:#333;}#mermaid-svg-KPy5OoiGFh7s3kSn .node rect,#mermaid-svg-KPy5OoiGFh7s3kSn .node circle,#mermaid-svg-KPy5OoiGFh7s3kSn .node ellipse,#mermaid-svg-KPy5OoiGFh7s3kSn .node polygon,#mermaid-svg-KPy5OoiGFh7s3kSn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KPy5OoiGFh7s3kSn .rough-node .label text,#mermaid-svg-KPy5OoiGFh7s3kSn .node .label text,#mermaid-svg-KPy5OoiGFh7s3kSn .image-shape .label,#mermaid-svg-KPy5OoiGFh7s3kSn .icon-shape .label{text-anchor:middle;}#mermaid-svg-KPy5OoiGFh7s3kSn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KPy5OoiGFh7s3kSn .rough-node .label,#mermaid-svg-KPy5OoiGFh7s3kSn .node .label,#mermaid-svg-KPy5OoiGFh7s3kSn .image-shape .label,#mermaid-svg-KPy5OoiGFh7s3kSn .icon-shape .label{text-align:center;}#mermaid-svg-KPy5OoiGFh7s3kSn .node.clickable{cursor:pointer;}#mermaid-svg-KPy5OoiGFh7s3kSn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KPy5OoiGFh7s3kSn .arrowheadPath{fill:#333333;}#mermaid-svg-KPy5OoiGFh7s3kSn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KPy5OoiGFh7s3kSn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KPy5OoiGFh7s3kSn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KPy5OoiGFh7s3kSn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KPy5OoiGFh7s3kSn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KPy5OoiGFh7s3kSn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KPy5OoiGFh7s3kSn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KPy5OoiGFh7s3kSn .cluster text{fill:#333;}#mermaid-svg-KPy5OoiGFh7s3kSn .cluster span{color:#333;}#mermaid-svg-KPy5OoiGFh7s3kSn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KPy5OoiGFh7s3kSn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KPy5OoiGFh7s3kSn rect.text{fill:none;stroke-width:0;}#mermaid-svg-KPy5OoiGFh7s3kSn .icon-shape,#mermaid-svg-KPy5OoiGFh7s3kSn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KPy5OoiGFh7s3kSn .icon-shape p,#mermaid-svg-KPy5OoiGFh7s3kSn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KPy5OoiGFh7s3kSn .icon-shape .label rect,#mermaid-svg-KPy5OoiGFh7s3kSn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KPy5OoiGFh7s3kSn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KPy5OoiGFh7s3kSn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KPy5OoiGFh7s3kSn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
🔴 RED
写一个失败的测试
🟢 GREEN
写最小代码让测试通过
🔵 REFACTOR
重构代码,保持测试通过
还有功能
需实现?
✅ 功能完成
TDD 示例流程:
cpp
// Step 1 🔴: 写失败的测试
TEST(StringUtilTest, ReverseReversesString) {
EXPECT_EQ(Reverse("hello"), "olleh");
}
// 编译失败: Reverse 未定义
// Step 2 🟢: 写最小实现
std::string Reverse(const std::string& s) {
return std::string(s.rbegin(), s.rend()); // 通过!
}
// Step 3 🔵: 加新测试
TEST(StringUtilTest, ReverseHandlesEmpty) {
EXPECT_EQ(Reverse(""), "");
}
TEST(StringUtilTest, ReverseHandlesPalindrome) {
EXPECT_EQ(Reverse("racecar"), "racecar");
}
// 当前实现自然通过 → 无需修改
// Step 4 🔴: 边界测试
TEST(StringUtilTest, ReverseHandlesUnicode) {
EXPECT_EQ(Reverse("你好"), "好你");
}
// 失败! → 需要改进实现
12. 快速参考卡片
断言宏速查
| 类别 | 宏 | 用途 |
|---|---|---|
| 真值 | EXPECT_TRUE(c) / ASSERT_TRUE(c) |
断言为 true |
EXPECT_FALSE(c) / ASSERT_FALSE(c) |
断言为 false | |
| 比较 | EXPECT_EQ(a,b) EXPECT_NE(a,b) |
== / != |
EXPECT_LT(a,b) EXPECT_LE(a,b) |
< / <= | |
EXPECT_GT(a,b) EXPECT_GE(a,b) |
> / >= | |
| 字符串 | EXPECT_STREQ(a,b) / EXPECT_STRNE(a,b) |
C 字符串 == / != |
EXPECT_STRCASEEQ(a,b) |
忽略大小写 | |
| 浮点 | EXPECT_NEAR(a,b,e) |
绝对误差 e |
EXPECT_THAT(a, DoubleNear(b,e)) |
相对误差 | |
| 异常 | EXPECT_THROW(s, T) / EXPECT_NO_THROW(s) |
抛/不抛异常 |
EXPECT_ANY_THROW(s) |
任意异常 | |
| 死亡 | EXPECT_DEATH(s, r) |
断言死亡 + stderr 匹配 |
EXPECT_EXIT(s, p, r) |
断言退出 + 退出码 + stderr | |
| HRESULT | EXPECT_HRESULT_SUCCEEDED(e) / FAILED(e) |
Windows COM/HRESULT |
TEST 宏速查
| 宏 | 用途 | 示例 |
|---|---|---|
TEST(suite, name) |
基本测试 | TEST(MathTest, Add) |
TEST_F(fixture, name) |
夹具测试 | TEST_F(VectorTest, Sort) |
TEST_P(fixture, name) |
参数化测试 | TEST_P(MathParamTest, Multiply) |
TYPED_TEST(fixture, name) |
类型参数化测试 | TYPED_TEST(ContainerTest, Size) |
INSTANTIATE 宏速查
| 宏 | 用途 |
|---|---|
INSTANTIATE_TEST_SUITE_P(prefix, fixture, generator) |
实例化 TEST_P |
INSTANTIATE_TEST_SUITE_P(prefix, fixture, generator, name_fn) |
带命名函数 |
INSTANTIATE_TYPED_TEST_SUITE_P(prefix, fixture, types) |
实例化 TYPED_TEST |
gmock 核心宏速查
| 宏/API | 用途 |
|---|---|
MOCK_METHOD(R, name, (args), (qualifiers)) |
定义 Mock 方法 |
EXPECT_CALL(obj, method(args)) |
设置期望 |
ON_CALL(obj, method(args)) |
设置默认行为 |
.Times(n) |
调用次数 |
.WillOnce(action) |
单次行为 |
.WillRepeatedly(action) |
重复行为 |
Return(v) |
返回值 |
ReturnRef(v) |
返回引用 |
Throw(ex) |
抛出异常 |
_ |
万能匹配器 |
13. 参考资源
| 资源 | 链接 |
|---|---|
| Google Test 官方文档 | https://google.github.io/googletest/ |
| Google Test GitHub | https://github.com/google/googletest |
| gmock Cookbook | https://google.github.io/googletest/gmock_cook_book.html |
| gmock Cheat Sheet | https://google.github.io/googletest/gmock_cheat_sheet.html |
| Advanced gtest Topics | https://google.github.io/googletest/advanced.html |
| CMake + gtest 集成 | https://google.github.io/googletest/quickstart-cmake.html |
推荐工具
- VSCode + Google Test Adapter ------ 在编辑器内直接运行/调试测试
- CLion ------ 内置 gtest 支持和覆盖率可视化
gcovr/lcov------ C++ 代码覆盖率收集ctest --output-on-failure -j $(nproc)------ 并行运行所有测试--gtest_repeat=100 --gtest_break_on_failure------ 定位 flaky test