用例分级体系:冒烟、回归、全量背后的成本逻辑
新板子焊好,第一件事不是跑功能,而是上电,看冒不冒烟——不冒烟,才值得往下测。软件测试借用了这个思路:每次提交,先用最小的一组用例回答一个问题——”这个版本是不是明显坏的?“如果是,后面所有测试都别浪费资源了。
但冒烟只回答了”五分钟之内查什么”。一个真实项目的用例库有几百上千条:夜间跑什么?周末跑什么?发版前跑什么?这篇文章把三件事串起来讲:冒烟这一层怎么设计,分级背后的成本账怎么算,以及落地手段——一套用例库怎么跑出 N 种跑法。
一、冒烟测试:上电,看冒不冒烟
冒烟要回答的唯一问题是”这个版本是不是明显坏的”。围绕这个问题,它有五条设计原则:
| 原则 | 要求 | 反面教材 |
|---|---|---|
| 快 | 总时长 5~10 分钟硬上限(MR 反馈超过 10 分钟,开发者就会绕过门禁) | 冒烟跑着跑着膨胀到 40 分钟 |
| 广而不深 | 每个关键模块碰一下,不追求边角 | 在冒烟里测某模块的 50 个边界条件 |
| 只抓”灾难级”故障 | 目标故障类型:起不来、连不上、核心路径断、数据全错 | 想抓逻辑细节错误(那是全量回归的活) |
| 零 flaky 容忍 | 冒烟里出现一次不稳定用例,立刻隔离或修——冒烟红必须等于”真坏了” | 团队养成”冒烟红了重跑一遍就好”的习惯,门禁就此失效 |
| 每次提交必跑 | 是门禁(gate),不过就合不进去 | 变成”参考性检查”,红了也能合并 |
用例怎么选?从全量库里筛那 20 条,先过三问(都答”是”才进冒烟):
- 这条挂了 = 版本基本不可用吗?(P0 级核心旅程:能启动、能通信、核心功能走通)
- 它历史上抓到过真 bug 吗?(从没失败过的用例是”装饰”,优先级往后排)
- 它跑得快且稳吗?(单条 ≤30 秒、连续百次零波动)
再按覆盖面补齐:每个关键模块/接口至少一条——覆盖的是”面”不是”点”。典型配比:启动/构建类 20%、核心功能 happy path 50%、关键接口 20%、一条端到端 10%。
二、一个 5 分钟的冒烟长什么样
嵌入式/汽车场景的时间预算示例(5 分钟盘):
0:00-0:30 拉代码 + 增量编译(ccache/分布式编译命中)
0:30-1:00 静态检查快档(MISRA 高危规则子集,不是全量)
1:00-2:00 单元测试冒烟档(GTest 按标签筛 ~200 条,并行跑)
2:00-2:30 vECU 启动测试:镜像加载、调度器起 tick、自检通过
2:30-4:00 总线/诊断 sanity:vcan 上收发一帧、UDS 0x10 切会话 +
0x22 读版本 + 0x27 解锁一轮(三个服务能应答就证明诊断栈活着)
4:00-5:00 一条端到端 SIL 场景:回放 10 秒数据 → verdict 判定 → 报告生成
每一条失败都直接说明”哪个大器官坏了”——这就是”广而不深”的意义。
让它”快”的工程手段:
- 并行化:用例间无依赖全部并行(如 gtest-parallel、CTest -j、pytest -n)
- 增量构建 + 编译缓存:ccache/sccache,冒烟绝不做全量 clean build
- 环境预热:容器镜像预构建、测试环境池常驻——冒烟的 setup 时间经常比测试本身长,这是第一优化对象
- fail-fast:一条挂了立刻终止并报红,不等跑完
- 依赖打桩:冒烟里绝不调真实外部服务/真实台架,全用 stub
- 超时熔断:每条用例配硬超时,卡死的用例按失败处理并标记
两条决定冒烟生死的纪律:
- 运行时膨胀监控:冒烟总时长进看板,每周超预算就砍——冒烟的最大死因是”大家都想往里塞用例”,要有明确的进出评审:进一条,必须说清它抓哪种灾难级故障;
- 失败归因洁癖:冒烟失败必须 100% 可归因到产品问题。环境抖动导致的失败要修环境,不许”重跑通过”糊弄过去——否则三个月后没人再信冒烟。
落地到 CI 很直接:MR 流水线挂冒烟这一个命令,超时 8 分钟兜底;退出码定好门禁语义——0 放行、2 拦产品问题、3 拦工具问题(把 3 单独暴露给值班人,别让它污染”产品挂了”的信号)。
一句话总结:冒烟的功力不在”测什么”,而在”忍住不测什么”——它是一层筛子,筛孔大小决定整条流水线的吞吐;孔太大漏掉灾难,孔太小堵住 MR。
三、分级的本质:一笔经济账
冒烟只解决了”每次提交的 5 分钟”。剩下的用例谁什么时候跑?要回答这个问题,先看清分级的本质——它不是”用例有轻重之分”,而是一个经济学结构:
每条用例的成本随执行频率线性放大,而它提供的信息价值随等待时间衰减——分级就是按”单位成本的信息收益”给用例排班。
先看成本。一条用例的真实价格远不止”跑一遍的机时”:
- 执行成本:机时(HIL 最贵)或 CPU 时间;
- 机会成本:它占着机柜/CI runner 时,别的用例在排队;
- 分析成本:失败了要人看。flake 失败最贵——烧了机时,还烧工程师对红灯的信任;
- 频率放大器:以上全部 × 执行频率。一天跑 20 次的门禁用例,单价再低也是大项;一年跑一次的全量用例,单价贵也无所谓。
再看收益。一条用例此刻跑一遍的收益 = 发现回归的概率 × bug 的严重度 × 及时性:
- 发现概率差异巨大:核心路径用例每次改动都可能挂,边角场景一年挂不了一次,”每次执行期望收益”差几个数量级;
- 及时性是隐藏主变量:bug 在引入后 5 分钟发现 vs 一周后发现,修复成本差一个数量级——引入者还记得上下文、改动还没被后续提交埋住。汽车行业再放大一档:到 HIL 才发现 = 机柜排期灾难;
- 所以同一条用例,放门禁里跑的价值 > 放夜间 > 放周末——信息在贬值。
四、三级各自买的是什么
| 级 | 预算约束 | 买什么 | 选例原则 |
|---|---|---|---|
| 冒烟(门禁/每次提交) | 5~10 分钟 | 即时反馈:5 分钟内告诉提交者”你搞坏了主干” | 失败概率密度最高 + 跑得最快,历史 bug 高发区 |
| 回归(夜间) | 一个夜间窗口 | 每日基线:系统整体今天还行不行 | 主干全部 + 历史缺陷用例 + 需求覆盖 |
| 全量(周末/发版) | 周末 / 发版窗口 | 覆盖率证据和长尾 | 一切,包括一年挂不了一次的边角 |
反直觉的一点:分级的分界线是”反馈时效”,不是”用例重要性”。 冒烟集不是”最重要的用例”,是”性价比最高的早警系统”——一条重要但要跑 40 分钟的用例,放冒烟里是负收益(拖垮反馈环),放夜间正好。
所以冒烟集别拍脑袋定:把用例按”历史失败次数 ÷ 执行时长”排序,前 20% 就是候选——用真实数据画分级边界,比评审会上拍出来的靠谱。
五、三条工程推论
- 门禁时长上限是工程师的耐心,不是技术约束。 5~10 分钟是心理阈值——超过就会被绕过(本地不跑、直接推、跳过检查)。门禁一旦被绕过,整套体系崩溃。所以冒烟集超时的修法是减用例,不是延阈值;
- 分级是活的,用例要在级之间流动:flake 的降级(它烧信任,留着是负资产)、新抓到的 bug 的用例升级进回归(历史缺陷是最准的”失败概率”预测器)、多年安静的边角降级到全量。KPI 看缺陷逃逸率:逃逸到下一级的 bug 越多,说明上一级筛选失灵;
- 合规约束高于经济约束:ASIL 等级高的用例不参与降级——它的收益不体现在”发现 bug 的期望”上,体现在”审核时必须出示执行证据”上。所以全量层不可省略,分级是”各就各位”,不是”砍掉贵的”。
六、汽车行业:同一套账,被机时放大
SIL 层这套账已经成立;到 HIL 层被机时放大 10~100 倍——HIL 的”全量”被压成”周末/发版”事件。所以汽车行业的分级比互联网行业更陡、更刚性:SIL 每晚全量、HIL 每晚精选、全量攒到发版前——同一套成本逻辑,两个环境的执行参数不同而已。
七、落地手段:给每条用例打标签
账算完了,怎么落地?答案是分层标记:给每条用例打多维正交标签,”跑哪些”变成按标签表达式筛选。 SIL 全量、HIL 子集、CI 冒烟、发版回归,都只是同一用例库的不同视图——不是四个用例库。标签不是分类学,是排班表。
SIL/HIL 场景尤其需要它:
- HIL 机时贵:机柜几班倒排队,跑不起全量,必须能筛出”HIL 必跑集”;
- 用例跨环境复用:MIL 能跑的 SIL 大多能跑,SIL 能跑的 HIL 未必能跑(依赖真实 ECU、故障注入硬件)——需要环境标签区分”能跑”和”该跑”;
- CI 分层:门禁 5 分钟冒烟、夜间回归、发版全量,三种颗粒度共用一套库;
- 26262 追溯:用例必须链需求 ID。
八、标签维度:正交,别揉成一个枚举
| 维度 | 典型取值 | 回答的问题 |
|---|---|---|
| level | unit / component / integration / system | 在 V 模型哪层 |
| env | mil / sil / pil / hil(多值) | 在哪些环境可执行 |
| scope | smoke / nightly / regression / release | 什么时候跑 |
| req | REQ-AEBS-003 | 追溯到哪条需求 |
| asil | QM / A / B / C / D | 评审和覆盖率要求 |
筛选是布尔表达式:env:hil AND scope:nightly AND NOT known_issue。env 必须是多值——同一条 AEB 用例往往 SIL、HIL 都要跑。
九、实现手段,从土到洋
(a) 测试框架自带机制
-
GoogleTest:没有原生 tag,工业惯例是套件名编码标签 +
--gtest_filter通配:# 套件命名: TEST(Smoke_Engine, Start)、TEST(Hil_Aebs, ...) ./tests --gtest_filter='Smoke_*:*Hil_*-*Slow*' # 正模式:负模式关键技巧
GTEST_SKIP():运行时探测环境,HIL 机柜不在线就 skip 而不是 fail——这是”同一份二进制在两个环境都能跑”的核心手法。fail 是”产品错了”,skip 是”条件不具备”,CI 统计里两者含义完全不同; - Catch2 / doctest:原生 tag:
TEST_CASE("...", "[smoke][hil]"),命令行./tests "[smoke]~[slow]"; - pytest:
@pytest.mark.hil+-m "sil and not slow"; - 自研 runner:框架不管,自己定义——通常配下面的 manifest。
(b) Manifest 清单(工程主流)
用例元数据和代码分离,一张 tests/manifest.yaml:
- id: TC-ADAS-0042
path: tests/system/adas/test_aebs.lua # 或 gtest 套件名
level: system
env: [sil, hil]
scope: [nightly, regression]
req: [REQ-AEBS-003]
asil: B
runner 读 manifest → 按表达式筛选 → 生成 gtest filter / 用例列表 → 执行 → 把 req/asil 写进 JUnit XML 的 <properties> 回传 ALM(回传报文里每个 <testcase> 都必须带需求 ID)。
好处:改标签不动代码;可加 lint 强制规则(如”asil:B 以上必须有 req 字段,否则 CI 打回”——正是”合规约束高于经济约束”的代码化:合规用例不参与经济性流动)。Lua 技术栈里还有个更轻的选择:文件头注释 -- @env sil,hil 由 runner 解析,和 manifest 二选一。
(c) 测试管理工具集中管
ECU-TEST 的 package attribute、TestRail / Polarion 自定义字段——本质是 manifest 的数据库版,执行器从工具拉筛选结果。小团队 (b) 够用,主机厂 (c)。
起步原则:先 env + scope + req 三个维度够用,维度多了没人维护。
十、标签和 XIL:一个管”该不该跑”,一个管”能不能跑”
XIL 标准管”脚本怎么访问台架”,不管用例管理;分层标记在 XIL 之上的用例描述层。两者配合:标签决定”该不该跑”,XIL 的环境抽象决定”能不能跑”——同一个脚本,SIL 里连桩、HIL 里连机柜,env: [sil, hil] 两边各跑一遍。(供应链之间交换用例描述还有 ASAM ATX 格式,了解即可。)
回看整条链:冒烟是筛子,分级是排班,标签是排班表。 三者不是三个孤立话题,是同一套成本逻辑从理念到落地的三层。
附:名词解释(按出场顺序)
| 名词 | 通俗解释 |
|---|---|
| 冒烟测试(smoke test) | 用最小的一组用例快速判断”这个版本是不是明显坏了”;名字来自硬件上电看冒不冒烟 |
| 门禁(质量门禁) | CI 流水线上的自动关卡:不达标就不许合入 |
| MR | Merge Request,合并请求;门禁通常挂在 MR 流水线上 |
| flake / flaky(不稳定用例) | 同一个用例不改代码、时而过时不过的”神经刀”用例 |
| happy path | 一切输入正常时的主流程用例,不碰异常分支 |
| MISRA | 汽车嵌入式 C/C++ 编码规范,静态检查常按它出规则 |
| vECU | 虚拟 ECU:在 PC 上仿真运行的控制器软件栈 |
| UDS | 统一诊断服务(ISO 14229):0x10 切会话、0x22 读数据、0x27 安全解锁都是它的服务 ID |
| SIL / HIL | 软件在环 / 硬件在环;同族还有 MIL(模型在环)、PIL(处理器在环) |
| 缺陷逃逸率 | 上一级没拦住、漏到下一级才发现的 bug 占比;衡量分级筛选是否失灵 |
| ASIL | 汽车安全完整性等级(QM/A/B/C/D,D 最严),ISO 26262 的核心概念 |
| ISO 26262 | 汽车功能安全标准,要求用例可追溯到需求 |
| GTEST_SKIP() | GoogleTest 的运行时跳过宏:条件不具备时记 skip 而非 fail |
| manifest(用例清单) | 与代码分离的用例元数据文件,标签都写在里面 |
| JUnit XML | 测试结果的通用上报格式,CI 和测试管理工具都认 |
| ALM | 应用生命周期管理工具链,需求—用例—结果的追溯归它管 |
| ASAM ATX | 供应链之间交换测试用例描述的标准格式 |
下一步:
查看全部文章