33、自定义测试标签:为测试用例设置标签,分组运行
测试用例一多,管理就成了头疼事。
我记得刚带团队那会儿,项目里有三百多个测试用例。每次跑测试都得等半小时,因为所有用例都混在一起跑。后来我琢磨着,要是能把测试用例分分组,比如「核心功能」一组、「边界条件」一组、「性能测试」一组,那该多好?
CMake 的 CTest 其实早就想到了这一点——测试标签(LABELS)。说白了,就是给每个测试用例贴个标签,然后按标签分组运行。
33.1 什么是测试标签?
测试标签是 CTest 提供的一种元数据机制。你可以在 add_test() 之后,用 set_tests_properties() 给测试用例设置 LABELS 属性。
一个测试用例可以贴多个标签。比如一个测试既属于「核心功能」,又属于「快速验证」,那就给它两个标签。
核心概念:标签就是测试用例的「分类牌」。你可以按模块、按优先级、按运行时长、按任何你想要的维度来分类。
33.2 基本用法:给测试打标签
先看一个最简单的例子。假设我们有三个测试文件:test_math.cpp、test_io.cpp、test_network.cpp。
# CMakeLists.txt
enable_testing()
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 "core;math")
set_tests_properties(test_io PROPERTIES LABELS "core;io")
set_tests_properties(test_network PROPERTIES LABELS "network;slow")
这里我用了分号分隔多个标签。CMake 里列表就是用分号隔开的,所以 "core;math" 表示两个标签:core 和 math。
小技巧:我个人习惯把标签统一用小写字母,避免大小写混用导致筛选时漏掉。比如 "Core" 和 "core" 在 CTest 里是区分大小写的。
33.3 按标签运行测试
标签打好了,怎么用?
在命令行里,用 -L 或 --label-regex 参数来筛选:
# 只运行 core 标签的测试
ctest -L core
# 运行 core 或 math 标签的测试(正则匹配)
ctest -L "core|math"
# 排除 network 标签的测试
ctest -L "core|io" -LE network
-L 是包含匹配,-LE 是排除匹配。这两个参数都支持正则表达式,所以你可以写很灵活的筛选条件。
举个例子,我想跑所有「不是慢速测试」且「属于核心模块」的用例:
ctest -L core -LE slow
嗯,这里要注意:-LE 的优先级高于 -L。如果一个测试同时匹配了 -L 和 -LE,它会被排除掉。
33.4 在 IDE 中按标签筛选
如果你用 CLion 或 Visual Studio,标签筛选也很方便。
在 CLion 的 CTest 面板里,有个筛选框,直接输入标签名就能过滤。我经常在调试阶段只跑 core 标签的测试,等提交前再跑全量测试。
曾经踩过的坑:有一次我在 CLion 里筛选标签,发现怎么都筛不出来。折腾了半天,才发现是标签名写错了——我在 CMakeLists.txt 里写的是 "core",但筛选时写的是 "Core"。大小写不一致,CTest 不会报错,但就是匹配不上。
33.5 标签的命名规范
标签怎么命名?我建议遵循以下原则:
| 维度 | 示例标签 | 说明 |
|---|---|---|
| 模块 | core, io, network, database | 按代码模块划分 |
| 优先级 | P0, P1, P2 | P0 是必须通过的 |
| 运行时长 | fast, medium, slow | 方便快速验证 |
| 测试类型 | unit, integration, e2e | 单元/集成/端到端 |
| 平台 | linux, windows, macos | 跨平台项目常用 |
你想想看,有了这些标签,你可以组合出各种运行策略:
# 快速验证:只跑核心模块的快速测试
ctest -L "core;fast"
# 完整验证:跑所有非慢速测试
ctest -LE slow
# 回归测试:跑所有 P0 和 P1 的测试
ctest -L "P0|P1"
33.6 在 CMake 中动态生成标签
手动给每个测试写标签,测试多了也累。我一般会用函数封装一下:
function(add_labeled_test test_name source_file)
add_test(NAME ${test_name} COMMAND ${test_name})
# 根据源文件路径自动推断模块标签
get_filename_component(test_dir ${source_file} DIRECTORY)
string(REPLACE "/" ";" path_parts ${test_dir})
list(GET path_parts -1 module_name)
# 设置默认标签
set(labels "${module_name};unit")
# 如果测试名称包含 "perf",加上性能标签
if(${test_name} MATCHES "perf")
list(APPEND labels "performance;slow")
endif()
set_tests_properties(${test_name} PROPERTIES LABELS "${labels}")
endfunction()
# 使用
add_labeled_test(test_math_calc "tests/math/test_math_calc.cpp")
add_labeled_test(test_io_read "tests/io/test_io_read.cpp")
add_labeled_test(test_network_perf "tests/network/test_network_perf.cpp")
这样,测试用例的标签就自动根据目录结构和命名规则生成了。我在项目中用了这套方案后,再也没手动写过标签。
33.7 标签与 CTest 的其它特性结合
标签还可以和 CTest 的其它功能配合使用:
- 与超时结合:给
slow标签的测试设置更长的超时时间 - 与资源分配结合:给
network标签的测试分配特定的网络资源 - 与并行运行结合:用
-j参数时,标签可以帮助你控制哪些测试可以并行
# 给慢速测试设置 120 秒超时
set_tests_properties(test_network_perf PROPERTIES
LABELS "network;slow"
TIMEOUT 120
)
# 给核心测试设置 4 个并行任务
ctest -L core -j 4
33.8 标签的调试与查看
有时候标签设置多了,想看看某个测试到底有哪些标签。可以用 -N 参数列出所有测试及其标签:
ctest -N
输出会显示每个测试的名称和标签。如果只想看某个标签下的测试:
ctest -L core -N
这样只列出 core 标签下的测试,不会真正运行它们。我在调试标签配置时经常用这个命令。
我的习惯:每次新增测试用例后,我都会跑一遍 ctest -N 确认标签正确。这花不了几秒钟,但能避免后续筛选时漏掉测试。
33.9 实战:一个完整的标签体系
最后,分享一个我在实际项目中用的标签体系。这个项目有 500+ 测试用例,分 8 个模块:
# 标签体系设计
# 模块标签: core, io, network, database, cache, security, api, ui
# 优先级标签: P0, P1, P2, P3
# 时长标签: fast(<1s), medium(1-10s), slow(>10s)
# 类型标签: unit, integration, smoke
# 日常开发:只跑当前模块的快速测试
ctest -L "core;fast" -j 8
# 提交前:跑所有 P0 测试
ctest -L "P0" -j 8
# 每日构建:跑所有非慢速测试
ctest -LE "slow" -j 16
# 完整回归:跑所有测试(通常在夜间执行)
ctest -j 32
这套体系用了两年多,团队里没人抱怨过测试管理的问题。说白了,标签就是给测试用例加了个「分类系统」,让不同场景下的人都能快速找到自己关心的测试。
嗯,关于测试标签,核心就这些。你可以在自己的项目里试试,从简单的模块标签开始,慢慢完善。记住,标签不是越多越好,够用就行。