一、CMake测试体系:从零到自动化
测试这件事,说实话,很多C/C++项目做得并不好。我见过太多项目,代码写得漂亮,但一说到测试就头疼。要么是手动跑测试,要么是测试脚本写得比代码还复杂。CMake的CTest模块,就是为了解决这个问题而生的。
我个人习惯在项目一开始就把测试框架搭好。为什么呢?因为后期再补测试,成本高得吓人。你想想看,一个项目几十万行代码,要回头加测试,光是梳理依赖关系就够你喝一壶的。
1.1 enable_testing():开启测试的开关
这个函数很简单,但很重要。它告诉CMake:嘿,我这个项目要跑测试了。
# 在顶层CMakeLists.txt中
cmake_minimum_required(VERSION 3.10)
project(MyProject C CXX)
enable_testing() # 开启测试支持
add_subdirectory(src)
add_subdirectory(tests)
注意,enable_testing()必须在顶层CMakeLists.txt中调用。我见过有人把它放在子目录里,结果CTest根本找不到测试。嗯,这是个坑。
1.2 add_test():注册你的测试用例
有了测试开关,接下来就是注册测试用例了。add_test()就是干这个的。
# tests/CMakeLists.txt
# 先编译测试可执行文件
add_executable(test_math test_math.cpp)
target_link_libraries(test_math PRIVATE mylib)
# 注册测试
add_test(NAME test_math_basic COMMAND test_math)
add_test(NAME test_math_advanced COMMAND test_math --advanced)
这里有个细节:NAME参数是测试的名字,COMMAND是运行测试的命令。你可以给同一个可执行文件传不同的参数,注册成多个测试用例。
我建议测试命名要有规律。比如用模块名_功能名_场景名的格式。这样当测试失败时,你一眼就能看出是哪个模块出了问题。
1.3 CTest配置:让测试跑得更聪明
光有测试还不够,你得告诉CTest怎么跑。比如并行测试、超时控制、测试标签等。
# 在CMakeLists.txt中配置CTest
set(CTEST_PARALLEL_LEVEL 4) # 并行跑4个测试
set(CTEST_TEST_TIMEOUT 60) # 每个测试超时60秒
# 给测试打标签
add_test(NAME test_unit_math COMMAND test_math)
set_tests_properties(test_unit_math PROPERTIES
LABELS "unit;math"
TIMEOUT 30
COST 2.0
)
为什么要打标签?因为你可以只跑特定标签的测试。比如提交代码前只跑unit标签的单元测试,晚上再跑integration标签的集成测试。
二、测试覆盖率:gcov + lcov 实战
测试覆盖率这东西,说白了就是看看你的测试到底覆盖了多少代码。我见过有人拍胸脯说测试很完善,结果一跑覆盖率,发现核心函数根本没测到。
2.1 开启覆盖率编译
gcov是GCC自带的覆盖率工具。要使用它,编译时需要加特殊参数。
# CMakeLists.txt中配置覆盖率
option(ENABLE_COVERAGE "Enable coverage reporting" OFF)
if(ENABLE_COVERAGE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -fprofile-arcs -ftest-coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
endif()
注意,--coverage是GCC的简写,等价于-fprofile-arcs -ftest-coverage。我个人习惯用--coverage,少打几个字嘛。
2.2 生成覆盖率报告
跑完测试后,会生成.gcda和.gcno文件。然后用lcov生成HTML报告。
# 生成覆盖率报告的命令
# 1. 收集原始数据
lcov --capture --directory . --output-file coverage.info
# 2. 过滤掉系统头文件和测试代码
lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file coverage_filtered.info
# 3. 生成HTML报告
genhtml coverage_filtered.info --output-directory coverage_report
这里有个坑:如果不过滤系统头文件,报告里会包含一大堆/usr/include下的代码,根本没法看。我刚开始用lcov时就犯过这个错,生成了一份几千页的报告,全是系统库的覆盖率。
- 行覆盖率(Line Coverage):代码行被执行的比例
- 分支覆盖率(Branch Coverage):if/else等分支被覆盖的比例
- 函数覆盖率(Function Coverage):函数被调用的比例
我个人认为,行覆盖率到80%以上算及格,90%以上算良好。但别盲目追求100%,有些错误处理代码很难触发。
三、CDash集成:让测试结果可视化
CDash是一个Web平台,用来收集和展示测试结果。你可以把它想象成测试报告的仪表盘。
3.1 配置CDash提交
要让CTest把结果提交到CDash,需要配置CTestConfig.cmake文件。
# CTestConfig.cmake
set(CTEST_PROJECT_NAME "MyProject")
set(CTEST_NIGHTLY_START_TIME "00:00:00 EST")
set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my-cdash-server.com")
set(CTEST_DROP_LOCATION "/submit.php?project=MyProject")
set(CTEST_DROP_SITE_CDASH TRUE)
然后在CI脚本中调用CTest:
# 提交到CDash的命令
ctest -D Experimental # 实验性构建
ctest -D Nightly # 每日构建
ctest -D Continuous # 持续构建
三种模式的区别:
| 模式 | 用途 | 我什么时候用 |
|---|---|---|
| Experimental | 开发人员手动提交测试结果 | 本地验证新功能时 |
| Nightly | 每天固定时间自动构建测试 | 团队每日集成检查 |
| Continuous | 代码提交后立即触发 | CI流水线中 |
3.2 CDash能告诉你什么
CDash的界面会展示:
- 哪些测试通过了,哪些失败了
- 测试耗时变化趋势
- 覆盖率变化曲线
- 不同平台、不同编译器的测试结果对比
我记得有一次,CDash显示某个测试在Linux上全过,但在Windows上全挂。一看日志,原来是路径分隔符的问题。这种跨平台问题,没有CDash很难及时发现。
四、知识体系总览
下面这张图,是我梳理的CMake测试体系的核心逻辑。你可以把它当作一个检查清单。
这张图展示了从开启测试到生成报告的全流程。说白了,就是三步:开启、注册、配置。但每一步都有很多细节,需要根据项目实际情况来调整。
好了,这一章的内容就到这里。测试是个持续改进的过程,没有银弹。关键是让团队养成写测试、看覆盖率、关注测试结果的习惯。工具只是辅助,人才是核心。