12、测试集成:enable_testing、add_test命令、CTest使用、测试覆盖率基础

说到测试,很多人的第一反应是「那是测试工程师的事」。但在我多年的项目经验里,恰恰是开发人员自己写的测试,最能发现代码的深层问题。CMake 内置的 CTest 工具,说白了就是帮你把测试这件事,无缝集成到构建流程里。你不用再手动跑一堆脚本,也不用担心忘了测某个模块。

这一章,我们就来聊聊怎么在 CMake 里把测试搭起来。我会从最基础的命令讲起,再到 CTest 的实际用法,最后聊聊测试覆盖率——嗯,这部分是我个人觉得最有意思的。

12.1 enable_testing:开启测试的大门

在 CMake 里启用测试,其实就一行命令:

enable_testing()

这行命令必须在顶层的 CMakeLists.txt 里调用。为什么?因为 CTest 需要知道整个项目的测试结构,而顶层目录是它的入口点。我曾经在一个子目录里调了 enable_testing(),结果 CTest 死活找不到测试——嗯,踩过这个坑之后,我再也没犯过。

注意: enable_testing() 必须在 add_test() 之前调用,否则测试不会被注册。这个顺序问题,我见过不少新手栽跟头。

12.2 add_test:注册你的测试用例

测试启用之后,下一步就是告诉 CMake:「嘿,这里有个测试要跑」。用 add_test 命令就行:

add_test(NAME <test_name> COMMAND <executable> [args...])

举个例子,假设你有一个可执行文件 test_math,它测试数学库的功能:

add_test(NAME test_math_add COMMAND test_math --add)
add_test(NAME test_math_sub COMMAND test_math --sub)

这里每个测试都有一个名字,方便你后续单独运行或筛选。我个人习惯把测试名字取得有规律一些,比如 模块名_功能名,这样一看就知道测的是什么。

小技巧: 如果你的测试程序需要传参数,直接在 COMMAND 后面跟上就行。CMake 会原样传递给测试程序。

12.3 CTest 使用:跑起来看看

测试注册好了,怎么跑?用 ctest 命令。你可以在构建目录里直接执行:

ctest

这会运行所有注册的测试。如果你想只看某个测试的结果:

ctest -R test_math_add

-R 参数支持正则表达式,非常灵活。比如 ctest -R "math" 会运行所有名字里带 math 的测试。

CTest 还支持并行运行测试:

ctest -j4

这个参数指定同时跑 4 个测试。如果你的测试之间没有依赖关系,并行能大幅缩短总时间。我记得有一次项目里有 200 多个测试,串行跑要 15 分钟,改成 -j8 后,3 分钟就跑完了——效率提升很明显。

CTest 的输出格式也可以定制:

ctest --output-on-failure

这个选项只会在测试失败时输出详细信息。平时跑测试,你只关心哪些挂了,对吧?

12.4 测试覆盖率基础

测试覆盖率,说白了就是「你的代码有多少行被测试执行到了」。覆盖率越高,你越有信心代码没大问题。但注意,100% 覆盖率不代表没有 bug——我见过覆盖率 95% 的项目,核心逻辑还是出了岔子。

在 CMake 里集成覆盖率工具,通常用 gcovlcov。你需要先开启编译器的覆盖率选项:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")

然后编译、运行测试,再用 lcov 生成报告:

lcov --capture --directory . --output-file coverage.info
genhtml coverage.info --output-directory coverage_report

这样会生成一个 HTML 报告,你可以用浏览器打开,直观地看到哪些代码被覆盖了,哪些没有。

核心要点: 测试覆盖率是衡量测试质量的一个指标,但不是唯一指标。我建议把覆盖率作为「底线」——比如要求核心模块不低于 80%,但不要盲目追求 100%。

12.5 知识体系图

下面这张图展示了本章的核心逻辑:从 enable_testing 开启测试,到 add_test 注册用例,再到 CTest 运行和覆盖率分析,整个流程一目了然。

CMake 测试集成流程 enable_testing() add_test() CTest 运行 测试覆盖率 关键命令 • enable_testing() • add_test(NAME ...) • ctest -R 正则 • ctest -jN 并行 • --output-on-failure 覆盖率工具: • gcov / lcov • genhtml 报告

12.6 实战建议

最后,分享几个我在项目中积累的经验:

  • 测试要早写:不要等代码写完了再补测试。我习惯在写功能代码的同时,就把测试用例写好。这样能及时发现设计上的问题。
  • 测试要独立:每个测试用例应该能独立运行,不依赖其他测试的结果或顺序。否则,一个测试挂了,后面一串跟着挂,排查起来很痛苦。
  • 覆盖率不是万能的:别被数字迷惑。我见过覆盖率 90% 的项目,核心逻辑的边界条件根本没测到。覆盖率报告要结合代码审查一起看。
  • CI 里跑测试:把 CTest 集成到持续集成流水线里,每次提交代码都自动跑一遍。这样能尽早发现问题,而不是等到上线前才手忙脚乱。
我的习惯: 在项目的 CMakeLists.txt 里,我会把测试相关的配置单独放在一个 if(BUILD_TESTING) 块里。这样用户可以通过 -DBUILD_TESTING=OFF 跳过测试构建,节省编译时间。

好了,这一章的内容就到这里。测试集成这件事,说白了就是「把测试变成构建的一部分」。你不需要记住所有命令的细节,但一定要理解这个流程:开启测试、注册用例、运行测试、分析覆盖率。把这四步走通了,你的项目质量就有了基本的保障。

公众号:蓝海资料掘金营,微信 deep3321