33、自定义测试标签:为测试用例设置标签,分组运行

测试用例一多,管理就成了头疼事。

我记得刚带团队那会儿,项目里有三百多个测试用例。每次跑测试都得等半小时,因为所有用例都混在一起跑。后来我琢磨着,要是能把测试用例分分组,比如「核心功能」一组、「边界条件」一组、「性能测试」一组,那该多好?

CMake 的 CTest 其实早就想到了这一点——测试标签(LABELS)。说白了,就是给每个测试用例贴个标签,然后按标签分组运行。

33.1 什么是测试标签?

测试标签是 CTest 提供的一种元数据机制。你可以在 add_test() 之后,用 set_tests_properties() 给测试用例设置 LABELS 属性。

一个测试用例可以贴多个标签。比如一个测试既属于「核心功能」,又属于「快速验证」,那就给它两个标签。

核心概念:标签就是测试用例的「分类牌」。你可以按模块、按优先级、按运行时长、按任何你想要的维度来分类。

33.2 基本用法:给测试打标签

先看一个最简单的例子。假设我们有三个测试文件:test_math.cpptest_io.cpptest_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" 表示两个标签:coremath

小技巧:我个人习惯把标签统一用小写字母,避免大小写混用导致筛选时漏掉。比如 "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

这套体系用了两年多,团队里没人抱怨过测试管理的问题。说白了,标签就是给测试用例加了个「分类系统」,让不同场景下的人都能快速找到自己关心的测试。

嗯,关于测试标签,核心就这些。你可以在自己的项目里试试,从简单的模块标签开始,慢慢完善。记住,标签不是越多越好,够用就行。

测试标签体系结构 CTest 测试标签 按模块划分 core, io, network database, cache 按优先级划分 P0, P1, P2, P3 按运行时长划分 fast, medium, slow 按测试类型划分 unit, integration smoke, e2e 组合使用:ctest -L "core;fast" -LE "slow" 日常开发:快速验证 ctest -L "fast" -j 8 提交前:回归验证 ctest -L "P0" -j 8 夜间:全量测试 ctest -j 32
公众号:蓝海资料掘金营,微信deep3321