Google Test 单元测试指南

核心目标:掌握 Google Test(gtest)与 Google Mock(gmock)框架的核心用法,学会编写高质量的 C++ 单元测试,建立稳定的测试驱动开发(TDD)工作流,并能将 gtest 集成到 CMake + CI/CD 流水线中。

前置知识:熟悉 C++14/17 基础语法;了解 CMake 基本用法(add_executabletarget_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
相关推荐
艾莉丝努力练剑2 小时前
【Linux:文件】Ext系列文件系统进阶
linux·运维·服务器·c++·文件系统·文件io·ext
basketball6164 小时前
C++ NULL 和 nullptr 区别 以及 nullptr 的核心实现
java·开发语言·c++
Fre丸子_5 小时前
自定义文件夹选取功能
c++
思麟呀7 小时前
C++工业级日志项目(六)异步日志器
linux·c++·windows
PAK向日葵8 小时前
从零实现 Python 虚拟机(二):S.A.A.U.S.O 的总体架构设计
c++·python
无限进步_8 小时前
【C++】weak_ptr、循环引用与线程安全
开发语言·数据结构·c++·算法·安全
咩咦8 小时前
C++学习笔记30:友元类、内部类和封装
c++·学习笔记·类和对象·封装·内部类·友元类·friend
黄小白的进阶之路9 小时前
C++提高编程---3.6 STL-常用容器-queue 容器【P213~P214】
c++
ID_180079054739 小时前
小红书评论 API 接口详解与实战开发
java·jvm·c++