第二十八章 工程化:CMake、单元测试、版本控制

← 返回目录

本章目标:学会组织真实项目——CMake 构建系统、 单元测试框架、代码组织、CI 思维。写完代码只是开始, 能"持续地、可验证地"维护才是工程师。

28.1 真实项目的目录结构(标准姿势)

myapp/
├── CMakeLists.txt        # 构建脚本

├── include/myapp/ # 头文件(公共接口)

│   └── math.hpp
├── src/                 # 源文件
│   ├── main.cpp
│   └── math.cpp
├── tests/               # 测试
│   ├── CMakeLists.txt
│   └── test_math.cpp
└── CMakePresets.json    # 可选的构建预设

规则:

28.2 CMake 基础(必会)

# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(myapp CXX)                    # 项目名 + 语言

set(CMAKE_CXX_STANDARD 26)            # C++26
set(CMAKE_CXX_STANDARD_REQUIRED ON)   # 强制
set(CMAKE_CXX_EXTENSIONS OFF)         # 不要 GNU 扩展

# 库 + 可执行文件
add_library(mymath src/math.cpp)      # 库
target_include_directories(mymath PUBLIC include)
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE mymath)

# 测试
enable_testing()
add_subdirectory(tests)

# tests/CMakeLists.txt
add_executable(test_math test_math.cpp)
target_link_libraries(test_math PRIVATE mymath)
add_test(NAME math COMMAND test_math)

构建命令(两种方式二选一):

# 方式1:Makefile
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build            # 跑测试

# 方式2:Ninja(更快,Termux 推荐)
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build

常用变量:

-DCMAKE_BUILD_TYPE=Debug/Release    # 调试/发布
-DCMAKE_CXX_FLAGS="-O2 -Wall"       # 附加编译参数
-DCMAKE_PREFIX_PATH=...             # 依赖查找路径

28.3 单元测试:三分钟上手

不用框架也能测(本书测试就是这么写的), 但工程上推荐框架。最流行两个:

Catch2 极简用法(v3):

#include <catch2/catch_test_macros.hpp>
#include "math.hpp"

TEST_CASE("加法", "[math]") {
    REQUIRE(add(2, 3) == 5);        // 断言:失败即中止本用例
    CHECK(mul(2, 3) == 6);          // 断言:失败继续跑
}

// 需要提供 main:
#define CATCH_CONFIG_MAIN            // 放在某个 cpp 顶部
#include <catch2/catch_all.hpp>

GoogleTest 极简用法:

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

测试三原则:

  1. 测试"行为"不测试"实现"(改名重构不破坏测试)
  2. 每个测试独立可重复(不依赖顺序、环境)
  3. 边界条件必测:空、0、负数、极大、非法输入

28.4 测试金字塔与 TDD(概念)

[手工/UI 测试]      ← 少
[集成测试]           ← 中
[单元测试]           ← 多(最重要、最快)

TDD(测试驱动开发):先写失败测试 → 写代码让它过 → 重构。不是所有人都用,但"写完立刻测"是底线。

覆盖率工具:llvm-cov(clang 自带):

clang++ -std=c++26 -fprofile-instr-generate -fcoverage-mapping ...
llvm-profdata merge ...
llvm-cov report ...

28.5 一个完整示例项目(测试文件里有全套)

第28章测试目录里会生成一个完整 mini 项目:

mini_project/
├── CMakeLists.txt
├── include/mymath/math.hpp
├── src/math.cpp
└── tests/test_math.cpp

构建并测试:

cd mini_project
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failure

亲手跑通它,你就掌握了现代 C++ 工程化的基本闭环。

28.6 调试:sanitizer 是你的保镖

AddressSanitizer(内存错误检测):

clang++ -std=c++26 -fsanitize=address,undefined -g app.cpp -o app
./app    # 越界/悬空/泄漏/UB 立刻报告并定位

日常开发建议:Debug 构建永远开 ASan。 它找不到真凶时的退路是 gdb(见附录 A)。

28.7 版本控制与协作(Git 速览)

git init
git add . && git commit -m "初始版本"
git log --oneline
git branch feature-x && git checkout feature-x
git merge feature-x

规范:

28.8 静态分析(编译器之外的把关人)

  clang-tidy src/math.cpp -- -std=c++26
  clang-format -i src/*.cpp

配合 CMake:

add_compile_options(-Wall -Wextra -Werror)  # 警告当错误

28.9 包管理:vcpkg / Conan(了解)

vcpkg(微软):C++ 的 npm

git clone https://github.com/microsoft/vcpkg
./vcpkg install fmt boost
# CMake 里自动 find_package

Conan(更灵活):配置复杂,团队/公司用得多。 知道"有这回事"即可,需要时再学。

28.10 陷阱清单

  1. 忘加 add_test → ctest 永远绿(测了个寂寞)
  2. 测试里断言全为 true → 假阳性(测试无效)
  3. Debug 忘开 ASan → bug 放跑
  4. 构建产物提交进 git
  5. 发布忘开 -O2/-flto/-DNDEBUG
  6. 头文件没有 include guard / pragma once
  7. 测试依赖"当前目录"(用绝对路径或临时目录)
  8. 不跑 ctest 就认为"测试过了"

本章小结

练习题(配套测试:测试_第28章_工程化.sh)


  1. 跑通 mini 项目的构建和测试。
  2. 故意改错一个断言,看 ctest 如何报失败。
  3. 故意写越界代码,看 ASan 报告。
  4. 用 clang-format 格式化一个文件,观察变化。
  5. git init 提交你的学习笔记。