14、测试框架集成:enable_testing与add_test、集成CTest、使用ctest命令运行测试并生成报告
说实话,很多开发者在CMake里写测试,就是简单加两行代码跑通了事。但真正到了项目大了、模块多了、跨平台了,你会发现——测试集成这件事,远不止「能跑」那么简单。
我最早接触CTest是在一个嵌入式项目里。当时团队有十几个人,每个人都在本地跑测试,结果五花八门。有人环境没配好,有人漏了依赖库,还有人测试用例压根没编译进去。后来我花了一周时间,把整个测试流程用CMake+CTest统一管理起来。嗯,从那以后,再也没人跟我说「我本地能跑啊」这种话了。
这一章,我们就来聊聊怎么把测试框架真正「焊」进你的构建系统里。
14.1 测试的起点:enable_testing
在CMake里启用测试,只需要一行命令:
enable_testing()
但这一行放在哪,有讲究。
关键规则:
enable_testing()必须在顶级CMakeLists.txt中调用- 它只能在
project()之后调用 - 一旦调用,整个构建树都会知道「我们要跑测试了」
14.2 注册测试用例:add_test
启用测试之后,下一步就是告诉CMake:你的测试用例在哪、怎么跑。
add_test(NAME <test_name> COMMAND <executable> [args...])
举个例子:
enable_testing()
# 假设我们有一个单元测试可执行文件
add_executable(test_math test_math.cpp)
target_link_libraries(test_math PRIVATE MathLib)
# 注册为CTest测试
add_test(NAME MathTest COMMAND test_math)
这里有个细节:COMMAND 后面跟的是可执行文件的名字。CMake会自动帮你找到构建目录下的对应二进制文件。
带参数的测试:
add_test(NAME MathTestWithArgs COMMAND test_math --verbose --iterations 100)
我个人习惯把测试参数写在 CMakeLists.txt 里,而不是硬编码在测试代码中。这样换环境时改起来方便,不用重新编译测试代码。
14.3 集成CTest:让测试跑得更聪明
CTest 是 CMake 自带的测试驱动工具。它不关心你用什么测试框架——Google Test、Catch2、Boost.Test,甚至你自己写的测试脚本,它都能管。
CTest 的核心能力:
- 自动发现所有通过
add_test注册的测试 - 并行执行测试(
-j参数) - 超时控制(
--timeout) - 输出格式控制(XML、JUnit等)
- 支持测试标签和分组
来看一个更完整的例子:
cmake_minimum_required(VERSION 3.16)
project(MyProject LANGUAGES CXX)
enable_testing()
# 添加测试可执行文件
add_executable(unit_test test_main.cpp test_foo.cpp test_bar.cpp)
target_link_libraries(unit_test PRIVATE MyLibrary GTest::GTest GTest::Main)
# 注册测试
add_test(NAME UnitTests COMMAND unit_test)
# 设置超时:每个测试最多跑30秒
set_tests_properties(UnitTests PROPERTIES TIMEOUT 30)
# 给测试打标签
set_tests_properties(UnitTests PROPERTIES LABELS "unit;core")
gtest_discover_tests() 自动发现所有 TEST 宏定义的用例。这样你只需要注册一个可执行文件,CTest 会自动拆分成多个测试用例来报告。
14.4 使用 ctest 命令运行测试
配置和构建完成后,在构建目录下执行:
ctest
就这么简单。但实际工作中,我几乎不会裸跑 ctest。下面是我常用的几种模式:
常用参数组合:
| 命令 | 说明 |
|---|---|
ctest -j4 |
4个任务并行跑,适合多核机器 |
ctest --output-on-failure |
只显示失败测试的详细输出 |
ctest -R "Math" |
只跑名字包含"Math"的测试 |
ctest -L "unit" |
只跑标签为"unit"的测试 |
ctest --timeout 60 |
每个测试超时60秒 |
ctest --test-dir build |
指定构建目录 |
我曾经在一个CI流水线上遇到过一个问题:测试跑着跑着就卡死了,整个流水线挂在那半小时。后来加了 --timeout 120,问题立刻暴露——有个测试用例死循环了。你看,有时候不是测试框架不行,是你没给它设个「闹钟」。
14.5 生成测试报告
光跑测试还不够,你得能「看见」结果。CTest 支持多种输出格式:
# 生成XML报告(Jenkins等CI工具常用)
ctest --output-junit report.xml
# 生成详细的文本日志
ctest --output-log test_log.txt
# 生成Dashboard提交用的XML
ctest -D Experimental
实战场景: 我在一个跨平台项目中,需要同时在Windows、Linux、macOS上跑测试。我写了一个简单的脚本:
#!/bin/bash
# 在每个平台上统一执行
mkdir -p build && cd build
cmake ..
cmake --build .
ctest --output-junit test_report.xml --output-on-failure -j$(nproc)
然后让CI系统收集 test_report.xml,直接生成可视化的测试趋势图。你想想看,每次提交代码后,测试通过率、耗时变化一目了然——这才是测试集成的真正价值。
14.6 知识体系总览
下面这张图,把本章的核心逻辑串起来了:
从图中可以看到,整个流程分三步:配置 → 构建 → 运行。每一步都有对应的CMake命令或ctest参数来精细控制。
14.7 避坑指南
最后,分享几个我踩过的坑:
- 测试找不到动态库: 如果你的测试链接了动态库,记得在
add_test之前设置CMAKE_RUNTIME_OUTPUT_DIRECTORY,或者用set_tests_properties设置ENVIRONMENT变量。 - 测试路径问题: 测试的工作目录默认是构建目录。如果你的测试需要读取数据文件,要么用绝对路径,要么在
add_test里用WORKING_DIRECTORY指定。 - 并行测试的资源冲突: 多个测试同时写同一个文件?加锁或者给每个测试分配独立的工作目录。
核心要点回顾:
enable_testing()必须在顶级 CMakeLists.txt 中调用add_test()注册测试,set_tests_properties()精细控制- CTest 支持并行、超时、标签过滤、多种报告格式
- 测试报告是CI/CD流水线的关键一环
好了,这一章的内容就到这里。测试集成这件事,说白了就是「让机器替你盯着代码质量」。配置好之后,每次构建自动跑一遍,比人工检查靠谱多了。