C++ 开发手册C++

CHAPTER 08生产层 · TESTING

测试与质量

GoogleTest、Catch2、mock、覆盖率与 TDD 实践。C++ 编译成本高,自动化测试能更早拦截回归。

开始阅读难度中级先修第七、八章约2小时
08.1

为什么需要测试

C++ 错误常在运行期才暴露,且编译慢、反馈周期长。自动化单测能在改动后秒级验证,避免"改一处崩一片"。

测行为边界

重点覆盖正常路径、边界值(0、空、最大)、错误输入,而非逐行实现。

08.2

GoogleTest 基础

GoogleTest 用 TEST 宏定义用例,EXPECT_* 失败继续、ASSERT_* 失败中止当前用例。配合 CMake 的 FetchContent 引入。

#include <gtest/gtest.h>
TEST(MathTest, AddPositive) {
  EXPECT_EQ(add(2, 3), 5);
  ASSERT_GT(add(1, 1), 1);
}
int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
08.3

测试夹具

用 TEST_F + 继承自 ::testing::Test 的类,在 SetUp/TearDown 中准备/清理共享状态,多个用例复用。

class StackTest : public ::testing::Test {
protected:
  void SetUp() override { s.push(1); }
  std::stack<int> s;
};
TEST_F(StackTest, PopReturnsTop) {
  EXPECT_EQ(s.top(), 1);
  s.pop();
  EXPECT_TRUE(s.empty());
}
08.4

Catch2 风格

Catch2 语法更紧凑,用 TEST_CASE 与自然的 REQUIRE 断言,自带 main,单头文件即可集成。

#define CATCH_CONFIG_MAIN
#include <catch2/catch_test_macros.hpp>
TEST_CASE("add works") {
  REQUIRE(add(2, 3) == 5);
}
选型

大型项目/需要强大 mock 选 GoogleTest;轻量、快速上手选 Catch2。两者都成熟,团队统一即可。

08.5

Mock 隔离

gMock 为接口创建模拟实现,设定期望与返回值,隔离数据库/网络等慢/不稳定依赖,验证交互。

class DBMock : public DB {
public:
  MOCK_METHOD(int, query, (int id), (override));
};
TEST(Service, UsesDb) {
  DBMock db;
  EXPECT_CALL(db, query(1)).WillOnce(testing::Return(42));
  EXPECT_EQ(service(db).get(1), 42);
}
08.6

覆盖率

用 gcov(GCC)或 llvm-cov(Clang)配合 --coverage 编译,生成覆盖率报告,定位未测代码。

g++ --coverage -O0 test.cpp -o test
./test
lcov --capture --directory . --output-file cov.info
genhtml cov.info --output-directory out
08.7

TDD 实践

测试驱动开发:先写失败测试 → 写最小实现让它通过 → 重构。小步快跑,确保每段逻辑都有测试守护。

步骤动作
红写一个会失败的测试
绿写最少量实现让测试通过
重构在测试保护下优化结构
08.8

常见陷阱与 FAQ

EXPECT 和 ASSERT 选哪个?

需要继续验证后续断言用 EXPECT;若后续断言依赖前置条件(如对象创建成功),用 ASSERT 提前中止,避免无意义崩溃。

测试编译太慢?

用预编译头、并行构建、把测试按模块拆分,或用 Catch2 单头文件减少翻译单元。CI 缓存构建产物。

覆盖率低但测试多?

可能测了易测的、漏了分支。关注"关键分支覆盖率"而非整体数字,并补边界与异常路径。

08.9

实战练习与资源

延伸资源