测试报告体系:从"一张 HTML"到"决策看板"

测试报告做到第五年,最大的变化是受众变了:早期报告是"给测试自己看的记录",现在是"给研发定位问题、给 PM 判断发版、给管理层看趋势"的决策工具。同一份数据,三种受众要三种视角。这篇讲报告体系的分层设计。

一、三层视角:L1 结论、L2 定位、L3 追溯

一份报告同时服务三种人,结构必须分层,而不是把所有信息堆在一个页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
L1 结论层(PM/管理层,10 秒看完)
├── 发版建议:GO / NO-GO / RISK-GO(显式结论,不是"请看详情")
├── 关键指标卡:通过率、新增失败、阻塞数、回归趋势 7 日曲线
└── 一句话摘要:自动生成("本次 612 用例,失败 3 个均为订单模块
已知问题 #4821,无新增阻塞,建议发版")

L2 定位层(研发,5 分钟定位)
├── 失败用例列表:按模块聚合,同类失败折叠
├── 每个失败:断言差异 diff + 失败时截图/控件树 + 相关日志 30 行
└── 环境指纹:git sha、数据版本、设备/机型、配置 hash

L3 追溯层(测试/审计,按需深挖)
├── 全量用例明细(含通过的)
├── 执行时序图:每用例开始/结束时间、重叠关系、排队等待
└── 原始产物索引:日志全文、录屏、请求响应 dump(按 run_id 归档)

最容易被忽略的是 L1 的"显式结论"。报告页面上写 600+ 行明细,但没人愿意替读者下结论——"能不能发版"是报告存在的理由。结论生成规则要写死:

1
2
3
4
5
6
7
8
9
10
def conclude(run, history):
blockers = [c for c in run.failed if c.severity == "blocker"]
if blockers:
return "NO-GO", f"{len(blockers)} 个阻塞级失败"
known = [c for c in run.failed if c.linked_issue]
if len(known) == len(run.failed):
return "RISK-GO", "失败均为已知问题"
if run.pass_rate >= history.p75_pass_rate:
return "GO", "通过率处于 7 日正常区间"
return "RISK-GO", "通过率低于近 7 日 P75,建议人工确认"

结论规则可审计(页面上展示"结论依据"),避免"报告说全绿但没人敢发"的信用损耗

二、失败聚合:把 40 个失败变成 3 个问题

研发最痛的不是"40 个用例失败",而是"40 个失败其实是 1 个根因"。聚合算法按失败指纹归组:

1
2
3
4
5
6
7
8
9
10
11
12
def fingerprint(case, failure):
"""失败指纹:模块 + 失败阶段 + 首个错误特征"""
return (
case.module, # 订单模块
failure.phase, # 支付步骤
normalize(failure.first_error), # "timeout waiting btn_pay visible"
)

# 归组后展示:
# [订单·支付] 37 个用例:等待支付按钮超时 → 关联 #4821(按钮文案改版)
# [消息·列表] 2 个用例:会话数断言 5 != 3 → 数据工厂配置未同步
# [环境] 1 个用例:设备 A-03 触摸无响应 → 设备故障(非产品问题)

归组后每组的"代表用例"只展开一个详情,其余折叠——研发按组修,不按用例修。指纹里的 normalize 很关键:把具体值抹掉(“等待 btn_X 超时” → “等待按钮超时”),否则同一根因会裂成 N 组。

三、趋势:单份报告没意义,曲线才有

单份报告回答"这次好不好",趋势回答"在变好还是变坏":

  • 通过率曲线:7/30/90 日,标注每次发版竖线——"这个版本之后通过率掉了 5%"比"本次通过率 92%"有信息量十倍;
  • 修复时长(MTTR):失败用例从"首次失败"到"首次转绿"的天数,按模块统计——某个模块 MTTR 从 2 天涨到 9 天,是"该模块测试债在积累"的先行指标;
  • flaky 率:同一用例同环境"绿红交替"的比例,>5% 的用例进 flaky 清单强制治理(修稳定或隔离),flaky 用例是报告可信度的毒药——团队一旦相信"那个用例本来就爱挂",所有报告都失去信用。

四、报告的工程实现

Flask 侧三个设计:

  1. 报告是纯静态产物:每次 run 结束,report_builder 把 L1/L2/L3 渲染成独立 HTML(数据内联,单文件可离线打开),落 var/results/<run_id>/report.html——不依赖服务端在线,链接发给任何人都能看;
  2. 数据与呈现分离:先落 result.json(结构化全量数据),HTML 只是它的视图——同一份 json 可以再生成钉钉摘要、Excel 明细、CI 门禁判定;
  3. 增量生成:用例逐例上报时增量更新 report.html(L2 失败列表实时可见),不用等全量跑完——3 小时的回归,第一个阻塞失败出现时研发就能开始看,不用等 3 小时后"报告出炉"。

五、小结

报告体系的核心是受众分层(L1 结论/L2 定位/L3 追溯)、失败聚合(按指纹归组,研发按组修)、趋势指标(通过率曲线 + MTTR + flaky 率)。报告的价值不在"好看",在于每个层的人都 10 秒内拿到自己要的东西——PM 拿到 GO/NO-GO,研发拿到 diff 和截图,审计拿到全量明细。