20、测试:enable_testing(),add_test(),使用CTest运行测试,测试超时与标签
测试这件事,说实话,在C++项目里经常被忽视。我见过太多项目,代码写得飞起,一跑测试就崩。嗯,我自己也踩过这个坑。今天我们就聊聊CMake里怎么把测试搞起来。
enable_testing():开启测试能力
这个函数很简单,但位置很关键。你必须在顶层的CMakeLists.txt里调用它,而且要在project()之后。
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
enable_testing() # 开启测试支持
add_subdirectory(src)
add_subdirectory(tests)
为什么必须放在顶层?因为CTest需要知道整个项目的测试结构。我曾经试过在子目录里调用enable_testing(),结果CTest根本找不到测试。说白了,这就是个全局开关。
add_test():注册测试用例
有了测试框架,接下来就是注册测试。最基础的方式是这样的:
add_test(NAME test_math COMMAND test_math_executable)
这里NAME是测试的名字,COMMAND后面跟的是可执行文件。你想想看,如果测试可执行文件需要参数怎么办?很简单:
add_test(NAME test_add COMMAND test_math_executable --add 2 3)
add_test(NAME test_sub COMMAND test_math_executable --sub 5 2)
我个人习惯把测试可执行文件统一放在tests/目录下,然后用add_test一个个注册。这样结构清晰,找问题也方便。
使用CTest运行测试
测试注册好了,怎么跑?两种方式:
- 命令行直接跑:在构建目录里执行
ctest - 通过CMake构建目标:执行
cmake --build . --target test
我个人更推荐第一种,因为CTest的参数更丰富。比如:
ctest -V # 显示详细输出
ctest -N # 只列出测试,不执行
ctest -R math # 只运行名字包含"math"的测试
ctest -E slow # 排除名字包含"slow"的测试
ctest --output-on-failure 可以让失败的测试输出详细信息,调试时特别有用。
测试超时:防止测试卡死
你有没有遇到过测试跑着跑着就卡住了?我有。有一次一个网络相关的测试,因为服务器没响应,硬是等了5分钟。从那以后,我养成了给测试加超时的习惯。
add_test(NAME test_network COMMAND test_network)
set_tests_properties(test_network PROPERTIES TIMEOUT 10)
这里设置了10秒超时。如果测试超过10秒还没结束,CTest会直接杀掉它,并标记为失败。
你也可以批量设置:
set_tests_properties(
test_network test_database test_api
PROPERTIES TIMEOUT 30
)
测试标签:给测试分类
项目大了以后,测试会越来越多。这时候标签就派上用场了。你可以给测试打上不同的标签,然后按标签筛选执行。
add_test(NAME test_unit_math COMMAND test_math)
set_tests_properties(test_unit_math PROPERTIES LABELS "unit;math")
add_test(NAME test_integration_db COMMAND test_db)
set_tests_properties(test_integration_db PROPERTIES LABELS "integration;database")
然后你可以这样运行:
ctest -L unit # 只运行单元测试
ctest -L integration # 只运行集成测试
ctest -L math # 只运行数学相关的测试
标签用分号分隔,可以打多个。我个人习惯给每个测试至少打两个标签:一个表示测试类型(unit/integration/e2e),一个表示测试模块(math/db/api)。这样筛选起来特别灵活。
实战示例:完整的测试配置
来看一个完整的例子,把上面这些知识点串起来:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(Calculator VERSION 1.0)
enable_testing()
add_executable(calc main.cpp add.cpp sub.cpp)
# 测试目录
add_subdirectory(tests)
# tests/CMakeLists.txt
# 编译测试可执行文件
add_executable(test_add test_add.cpp)
target_link_libraries(test_add PRIVATE calc_lib)
add_executable(test_sub test_sub.cpp)
target_link_libraries(test_sub PRIVATE calc_lib)
# 注册测试
add_test(NAME test_add_positive COMMAND test_add 2 3)
add_test(NAME test_add_negative COMMAND test_add -1 -2)
add_test(NAME test_sub_positive COMMAND test_sub 5 2)
add_test(NAME test_sub_negative COMMAND test_sub -3 -1)
# 设置超时
set_tests_properties(
test_add_positive test_add_negative
test_sub_positive test_sub_negative
PROPERTIES TIMEOUT 5
)
# 设置标签
set_tests_properties(test_add_positive test_add_negative PROPERTIES LABELS "unit;add")
set_tests_properties(test_sub_positive test_sub_negative PROPERTIES LABELS "unit;sub")
跑一下看看:
mkdir build && cd build
cmake ..
cmake --build .
ctest -V
- enable_testing() 必须在顶层CMakeLists.txt中调用
- add_test() 注册测试,NAME和COMMAND是必填参数
- TIMEOUT属性防止测试卡死,建议留余量
- LABELS属性给测试分类,方便筛选执行
- ctest -V 显示详细输出,-L按标签筛选,-R按名字筛选
测试这件事,说白了就是给代码上保险。刚开始可能觉得麻烦,但等你被线上bug坑过几次,就知道测试有多重要了。嗯,今天就聊到这,去给你的项目加几个测试吧。