第1章:测试——enable_testing、add_test、CTest基础与测试用例编写
说实话,很多做C++项目的朋友,对CMake的测试模块了解得不多。我刚开始接触CMake时,也觉得测试嘛,不就是跑几个可执行文件看看结果?后来在一个中型项目中,手动测试搞得我焦头烂额,才意识到CMake自带的CTest有多香。
这一章,我们就来聊聊CMake的测试体系。说白了,就是怎么用CMake帮你自动跑测试,省去你每次改完代码都要手动验证的麻烦。
1.1 测试的起点:enable_testing()
在CMake里启用测试,只需要一行命令:
enable_testing()
这行代码要放在顶层的CMakeLists.txt中。为什么?因为测试是全局的,不是某个子目录私有的。我见过有人把它放在子目录里,结果死活跑不起来——嗯,这是个坑。
调用之后,CMake就会在构建目录生成一个CTestTestfile.cmake文件。这个文件记录了所有测试信息,CTest就是靠它来发现测试的。
1.2 注册测试:add_test()
有了测试框架,接下来就是注册具体的测试用例。用 add_test() 命令:
add_test(NAME 测试名称 COMMAND 可执行文件 [参数...])
举个实际例子。假设你写了一个数学库,有个加法函数需要测试:
# 先编译测试程序
add_executable(test_add test_add.cpp)
target_link_libraries(test_add PRIVATE mathlib)
# 注册测试
add_test(NAME test_addition COMMAND test_add 1 2 3)
这里有个细节:COMMAND后面跟的是可执行文件路径。CMake会自动处理路径问题,你只需要写目标名称就行。
1.3 CTest基础:跑起来看看
测试注册好了,怎么跑?两种方式:
- 直接运行 ctest 命令:在构建目录下执行
ctest,它会自动发现所有测试并运行。 - 通过 make test:如果你用Makefile生成器,
make test也能触发CTest。
我个人更推荐直接用 ctest,因为参数更灵活。比如:
# 运行所有测试
ctest
# 只运行名字匹配的测试
ctest -R test_add
# 输出详细信息
ctest -V
# 并行运行,4个任务同时跑
ctest -j4
说到并行,我曾经在一个有200多个测试用例的项目里,单线程跑要等10分钟。加上 -j8 之后,2分钟就跑完了。你想想看,这效率提升多明显。
1.4 测试用例编写:从简单到实用
测试用例本身就是一个普通的可执行程序。它的核心逻辑是:运行被测试的代码,然后检查结果是否符合预期。
最简单的写法:
// test_add.cpp
#include <iostream>
#include <cstdlib>
int add(int a, int b) { return a + b; }
int main() {
if (add(2, 3) != 5) {
std::cerr << "FAIL: 2 + 3 should be 5" << std::endl;
return 1; // 返回非0表示失败
}
std::cout << "PASS" << std::endl;
return 0;
}
CTest判断测试是否通过的标准很简单:返回值是否为0。返回0表示通过,非0表示失败。所以你的测试程序最后一定要 return 0 或 return 1。
当然,实际项目中你不会手写这么多 if 判断。一般会用测试框架,比如 Google Test、Catch2 等。但底层原理是一样的——最终都是通过返回值告诉CTest结果。
1.5 知识体系总览
为了让你更直观地理解CMake测试的整体结构,我画了一张图:
从这张图可以看得很清楚:enable_testing() 是开关,add_test() 是注册,CTestTestfile.cmake 是桥梁,ctest 是执行者。四者缺一不可。
1.6 实战中的避坑指南
我在实际项目中踩过不少坑,挑几个典型的说说:
- 测试找不到可执行文件:add_test 里的 COMMAND 如果写的是相对路径,一定要确保路径正确。我建议直接用目标名称,CMake会自动处理。
- 测试一直通过但逻辑是错的:检查你的测试程序是否真的 return 了非0值。有时候忘记写 return 1,CTest就会误判为通过。
- 并行测试数据竞争:如果多个测试写同一个文件,并行跑时会互相干扰。解决办法是让每个测试使用独立的临时目录。
1.7 小结
这一章我们聊了CMake测试的三大基础:
- enable_testing():开启测试功能的开关,必须放在顶层CMakeLists.txt
- add_test():注册测试用例,指定名称和命令
- CTest:测试执行器,通过退出码判断通过/失败
测试用例的编写其实很简单——就是一个返回0或非0的可执行程序。但别小看这个基础,后面我们聊测试框架、覆盖率、自动化测试时,都是建立在这个基础之上的。
嗯,这一章就到这里。记住:测试不是可有可无的,它是代码质量的守门员。早点用上CTest,你会感谢自己的。
公众号:蓝海资料掘金营,微信deep3321