CTest标签与分组:set_tests_properties、LABELS属性、按标签运行测试
测试多了以后,你会面临一个很现实的问题——怎么挑着跑?
我最早接触CTest时,项目里只有十几个测试,一条命令全跑完,挺爽的。后来测试堆到两三百个,每次跑要等十几分钟。更烦的是,我只想跑单元测试,结果集成测试、性能测试全跟着跑了一遍。嗯,这时候就需要标签和分组了。
为什么需要标签?
说白了,标签就是给测试打上的分类标记。你可以按模块分、按类型分、按优先级分。想跑哪一组,就指定哪个标签。
我在一个嵌入式项目里遇到过这种情况:硬件相关的测试只能在开发板上跑,纯逻辑的测试在CI上就能跑。如果没有标签,每次都得手动挑测试,或者写一堆if判断。后来我用LABELS一标,世界清净了。
核心思路:用set_tests_properties给测试打标签,用ctest -L按标签筛选运行。
set_tests_properties 基础用法
这个命令可以修改测试的各种属性,LABELS只是其中之一。我们先看个最简单的例子:
add_test(NAME test_math COMMAND test_math)
add_test(NAME test_io COMMAND test_io)
add_test(NAME test_network COMMAND test_network)
set_tests_properties(test_math PROPERTIES LABELS "unit")
set_tests_properties(test_io PROPERTIES LABELS "unit")
set_tests_properties(test_network PROPERTIES LABELS "integration")
这样一标,test_math和test_io属于"unit"组,test_network属于"integration"组。
按标签运行测试
运行的时候,用-L参数指定标签:
# 只跑单元测试
ctest -L unit
# 只跑集成测试
ctest -L integration
# 排除某个标签(用 ! 前缀)
ctest -L "!slow"
注意,-L是正则匹配。如果你标签叫"unit",-L unit能匹配到,-L "unit.*"也能。我个人习惯用精确匹配,避免误伤。
小技巧:多个标签可以用|分隔,比如ctest -L "unit|integration",这样两个组的测试都会跑。
给测试打多个标签
一个测试可以属于多个分组。比如某个测试既是单元测试,又比较慢:
add_test(NAME test_big_matrix COMMAND test_big_matrix)
set_tests_properties(test_big_matrix PROPERTIES LABELS "unit;slow")
多个标签用分号分隔。这样:
ctest -L unit会包含它ctest -L slow也会包含它ctest -L "unit;slow"要求同时匹配两个标签(注意这是AND逻辑)
注意:CTest的标签匹配默认是OR逻辑。如果你想用AND逻辑,需要写-L "unit&slow"。这个语法有点反直觉,我第一次用的时候就踩坑了。
批量设置标签
如果你有一堆测试要打同一个标签,一个个写太累了。可以用set_tests_properties一次搞定:
# 假设有 test_a, test_b, test_c 三个测试
set_tests_properties(
test_a test_b test_c
PROPERTIES LABELS "unit"
)
或者用CMake的列表操作:
set(UNIT_TESTS
test_math
test_string
test_sort
test_search
)
foreach(test_name ${UNIT_TESTS})
add_test(NAME ${test_name} COMMAND ${test_name})
set_tests_properties(${test_name} PROPERTIES LABELS "unit")
endforeach()
我个人更喜欢第二种方式,因为测试列表清晰,后期维护方便。
标签命名规范
标签怎么起名?我建议遵循几个原则:
| 维度 | 示例标签 | 说明 |
|---|---|---|
| 按类型 | unit, integration, e2e, performance | 测试的性质 |
| 按模块 | module_io, module_net, module_gui | 测试所属模块 |
| 按速度 | fast, slow, nightly | 测试耗时 |
| 按平台 | linux, windows, embedded | 测试运行平台 |
| 按优先级 | p0, p1, p2 | 测试重要性 |
我曾经在一个项目里见过有人用中文标签,比如"单元测试"、"集成测试"。虽然CTest支持UTF-8,但命令行里切输入法很痛苦。我建议还是用英文小写加下划线,通用性最好。
实际项目中的标签策略
拿我之前参与的一个中间件项目举例,我们的标签体系是这样的:
# CMakeLists.txt 片段
add_test(NAME test_ring_buffer COMMAND test_ring_buffer)
set_tests_properties(test_ring_buffer PROPERTIES
LABELS "unit;fast;module_core"
)
add_test(NAME test_tcp_reconnect COMMAND test_tcp_reconnect)
set_tests_properties(test_tcp_reconnect PROPERTIES
LABELS "integration;slow;module_net"
)
add_test(NAME test_gui_render COMMAND test_gui_render)
set_tests_properties(test_gui_render PROPERTIES
LABELS "e2e;slow;module_gui;nightly"
)
这样,CI流水线里可以这样配置:
- 每次提交:
ctest -L "fast"—— 只跑快速测试,保证基本功能 - 每日构建:
ctest -L "nightly"—— 跑所有耗时测试 - 发布前:
ctest -L "unit|integration|e2e"—— 全量测试
经验之谈:标签不是越多越好。我见过有人给每个测试打了七八个标签,结果维护起来比写测试还累。一般来说,2-3个维度就够用了。
标签与CTest的其他属性配合
LABELS不是set_tests_properties能设置的唯一属性。常用的还有:
set_tests_properties(test_foo PROPERTIES
LABELS "unit" # 标签
TIMEOUT 30 # 超时时间(秒)
COST 2.5 # 测试成本(影响并行调度)
REQUIRED_FILES "data.txt" # 依赖文件
ENVIRONMENT "VAR=value" # 环境变量
FIXTURES_REQUIRED "db" # 依赖的fixture
)
标签和TIMEOUT配合使用效果很好。比如给"slow"标签的测试设置更长的超时时间:
set_tests_properties(test_heavy PROPERTIES
LABELS "slow"
TIMEOUT 120
)
用标签做测试分组管理
除了命令行筛选,标签还可以在CTestTestfile.cmake里做更精细的控制。比如你想把某个目录下的所有测试都打上同一个标签:
# 在子目录的 CTestTestfile.cmake 中
add_test(NAME test_sub_a COMMAND test_sub_a)
add_test(NAME test_sub_b COMMAND test_sub_b)
# 批量设置标签
get_property(tests DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY TESTS)
foreach(test_name ${tests})
set_tests_properties(${test_name} PROPERTIES LABELS "sub_module")
endforeach()
这个方法在大型项目中特别有用。你不需要在每个add_test后面都跟一个set_tests_properties,而是可以在文件末尾统一处理。
标签的调试技巧
有时候你忘了某个测试打了什么标签,可以用ctest -N配合--show-only查看:
# 列出所有测试及其标签
ctest -N --show-only=json-v1 | jq '.tests[] | {name: .name, labels: .properties.LABELS}'
如果没有jq,也可以用:
ctest -N --show-only=json-v1 > tests.json
然后打开JSON文件查看。嗯,这个方法虽然土,但管用。
快速检查:运行ctest -L "nonexistent_label",如果返回0个测试,说明你的标签写错了或者没匹配到。
SVG:CTest标签与分组核心逻辑
常见问题与避坑
问题1:标签名大小写敏感吗?
是的。LABELS属性是大小写敏感的。"Unit"和"unit"是两个不同的标签。我建议统一用小写,省得记混。
问题2:标签里能包含空格吗?
可以,但需要引号包裹。比如LABELS "my unit test"。不过我个人不建议这么做,命令行里处理空格很麻烦。
问题3:ctest -L 匹配不到测试?
我曾经遇到过这种情况:明明打了标签,但ctest -L xxx就是找不到。后来发现是CMake缓存问题。执行cmake --build .重新生成一下就好了。
避坑指南:如果你在子目录的CMakeLists.txt里用set_tests_properties,要确保add_test已经执行过了。CMake是声明式的,顺序很重要。我曾经把set_tests_properties写在add_test前面,结果标签根本没生效。
总结
标签这个东西,说白了就是给测试加个分类。用好了,你的测试管理会非常灵活。用不好,也就是多写几行代码的事。
我个人建议,从项目一开始就规划好标签体系。别等到测试堆到几百个了再回头补标签,那时候你连哪些测试该打什么标签都记不清了。
嗯,标签就聊到这里。记住三个核心命令:set_tests_properties打标签,ctest -L按标签跑,ctest -N --show-only查标签。够用了。
公众号:蓝海资料掘金营,微信deep3321