【AI Native开发-13】测试执行:从单用例到质量流水线
摘要
会调试单个失败之后,面对的是规模问题:一个功能几十上百个测试,一次改动可能影响任何地方。这一篇讲怎么把测试组织成分层、分批、可复现的执行体系,让”跑了测试”变成一份能支撑发布决策的证据。
核心论点:
- 测试要按反馈速度分层,不是一股脑全跑。 冒烟、受影响用例、全量回归三层,各有触发时机和用途。把慢测试放在每次保存时跑,团队就会不跑测试。
- 执行结果必须分类汇总,不能只看”绿没绿”。 通过、失败、跳过、未执行、阻塞——五类状态各有含义。尤其”跳过”和”未执行”,它们是绿色表象下的覆盖空洞。
- 要有明确的停止条件。 环境不稳、证据不足、高风险失败未定位时,必须停下来,绝不允许宣称”测试通过、可以发布”。
本篇给出三层执行金字塔、执行顺序、统一命令与报告格式、并行隔离纪律、停止条件,以及标准测试执行报告模板。
一个典型场景
删除功能做完了,同事问:“测了吗?“你让 AI”跑一下测试”。它跑了一会儿回:“通过了。”
你准备发布,多问了一句:“都跑了哪些?“它答:“删除相关的单元测试跑了,都过了。”
再追问发现:
- 接口测试根本没跑(AI 觉得单元测试过了就行);
- 两个 E2E 用例因为浏览器环境没起,被跳过了,但没说;
- 有一个测试标记了
skip(之前调试 flaky 留的),一直没恢复; - 类型检查和构建没跑——而上次正是生产构建才暴露了
article-loader的类型错误。
“通过了”这三个字,掩盖了大片根本没执行的验证。测试执行的价值不在于”跑了”,而在于”跑了该跑的、结果可核对、没跑的被显式知道”。
核心问题:为什么”跑测试”需要被设计
AI 执行测试有几个默认问题:
- 挑容易的跑:单元测试快、好跑,就只跑单元;E2E 慢、要起环境,就默认跳过;
- 报喜不报忧:倾向于报告”通过”,跳过的、没跑的、环境起不来的,一笔带过;
- 不区分批次:要么只跑一两个,要么全跑(全跑太慢就干脆不跑);
- 结果不可复现:这次跑这些、那次跑那些,没法回答”这个版本到底验证了什么”。
所以测试执行要像流水线一样被设计:什么时机跑哪一层、结果怎么记、什么情况必须停,都要有明确规则,而不是靠 AI 临场判断。
三层测试金字塔(按反馈速度分层)
/ E2E \ 慢(分钟级)、少、关键用户路径
/---------\\ 触发:发布前 / 全量回归
/ 接口/集成 \ 中速(秒级)、验证真实协作
/-------------\\ 触发:PR / 功能完成
/ 单元测试 \ 快(毫秒级)、多、纯逻辑
/-----------------\\ 触发:每次实现一小步 / 保存时第一层:冒烟测试(smoke)——最快、最核心
- 内容:系统最核心路径能不能跑通(启动、首页、关键 API 不崩);
- 时机:每次改动后、每次部署后立刻跑;
- 目的:快速发现”彻底坏了”,秒级反馈;
- 数量:极少,个位数。
第二层:受影响测试(affected)——按改动范围
- 内容:本次改动直接相关的单元 + 接口测试;
- 时机:实现每个小步、提 PR 时;
- 目的:验证改动本身正确,且没破坏直接关联的行为;
- 关键:要能识别”受影响范围”——改了删除逻辑,删除相关的单元和接口测试都要跑,相关的列表、文章加载测试也要跑。
第三层:全量回归(regression)——完整覆盖
- 内容:所有单元 + 接口 + E2E + 类型检查 + 构建;
- 时机:功能完成、发布前、合入主干前;
- 目的:证明改动没有破坏系统的任何其他部分;
- 特点:慢、全,是发布决策的最终依据。
纪律:不能用低层替代高层。 单元测试全绿 ≠ 接口没问题(前面那个权限被 mock 的例子);接口全绿 ≠ E2E 用户路径没问题。发布前必须跑全量回归,这是不可省略的一层。
执行顺序:从快到慢,从便宜到昂贵
一次完整验证,按这个顺序跑,让便宜的检查先拦截问题:
1. 静态检查 类型检查、lint —— 秒级,先抓低级错误
2. 单元测试 纯逻辑 —— 毫秒/秒级
3. 接口/集成测试 真实路由+中间件 —— 秒级
4. 构建 pnpm build —— 抓类型、导入、生产环境问题
5. E2E 测试 浏览器真实路径 —— 分钟级,放最后为什么构建排在 E2E 前、且必须单独跑?因为 Astro 静态站的 dev 模式和生产构建行为可能不同——前面的 article-loader 类型错误属于教学案例,article.status 则说明幻觉字段可能绕过业务校验;两者都提醒我们不能只看 dev。dev 绿不等于构建绿,构建是上线前的必过项。
顺序的意义:前面任何一层红了,就不用浪费时间跑后面更慢的层。类型都不过,跑 E2E 毫无意义。
统一命令、环境与报告格式
让 AI 执行测试时,要求统一,避免”这次用这个命令、那次用那个参数”。下面以一套典型的 Node/Astro 工程命令为例(vitest + playwright):
执行约定(示例命令,按你项目 package.json 里的实际 scripts 替换):
- 静态检查:pnpm lint && pnpm check(类型检查)
- 单元测试:pnpm test(vitest)
- 单个文件:pnpm test tests/api/delete.test.ts
- 单个用例:pnpm test -t "用例名"
- 构建: pnpm build
- E2E: pnpm test:e2e(playwright)
- 环境变量、测试数据准备方式固定,不临时改报告必须用统一格式分类汇总,五类状态一个都不能少:
执行批次:<冒烟 / 受影响 / 全量回归>
触发原因:<实现第 N 步 / PR / 发布前>
执行范围:<哪些目录 / 哪些用例>
结果统计:
- 通过(passed):42
- 失败(failed):1 ← 必须列出每个失败用例 + 关联 TEST 编号
- 跳过(skipped):2 ← 必须列出跳过原因(含历史遗留 skip)
- 未执行(not run):8 ← 必须说明为什么没跑(环境?太慢?依赖缺失?)
- 阻塞(blocked):0 ← 环境/fixture 故障导致无法运行
失败明细:
- TEST-002-2 普通用户删除:失败,期望 403 实得 200(疑似越权,已转调试)
跳过/未执行明细:
- TEST-004-1(E2E):未执行,Playwright 浏览器未安装
- TEST-xxx:skipped,原因 flaky 治理中(负责人 @xxx,登记于 xx 日)
结论:□ 可准出 ■ 不得准出(原因:存在失败 + 1 个 E2E 未执行)关键纪律:
- “跳过”和”未执行”必须显式列出,不能混进”通过”。一个被 skip 的用例等于没测,但如果报告里不显眼,它会被永久遗忘;
- 每个失败都要关联到 TEST 编号,这样能回溯到需求(REQ);
- 结论不能只写”通过”,要显式判断准出与否。
并行执行与数据隔离
测试多了会并行跑,并行带来数据污染风险。规则:
- 测试之间绝不共享可变数据:每个测试独立造数、独立清理
- 文件操作类测试用独立临时目录,不用共同的 fixtures 目录
- 端口、资源不冲突:并行服务用不同端口
- 测试数据带唯一标识(如 testId + 随机后缀),避免撞车
- afterEach / afterAll 严格清理,失败时也要清理(用 finally 语义)前面提过的”删除测试第二次跑失败”,根因就是多个测试共享同一篇真实文章。并行 + 共享数据 = flaky 和偶发失败的重灾区。数据隔离既是第 11 篇自动化用例的要求,也是并行执行的前提。
停止条件:什么时候不许宣称通过
这是执行纪律里最重要的一条。出现以下任一情况,必须停止,不得准出:
■ 有任何 P0/P1 用例失败且未定位根因
■ 失败被"重试后通过"绕过,但没确认是不是 flaky / 真 bug
■ 测试环境不稳定(环境失败反复出现)→ 先修环境,结果不可信
■ 关键 E2E / 接口用例"未执行"或"被跳过"且无审批
■ 类型检查或构建失败(dev 绿不算数)
■ 证据不足:无法确认本次跑的范围覆盖了改动影响面
■ 发现越权、数据丢失、资金类失败 → 立即停,升级处理反面做法(必须禁止):
- “大部分过了,应该没问题,先发吧”;
- “那个失败重跑一次过了,忽略”;
- “E2E 没环境没跑,单元测试过了就行”;
- “skip 的那几个回头再说”。
AI 特别容易在”差一点点全绿”时倾向于报告成功。停止条件就是给它划死的红线:证据链不完整,结论只能是”不得准出”。
测试集治理
随着用例增长,需要分类维护(第 16 篇团队化会展开):
冒烟集(smoke):个位数,核心路径,每次部署跑
黄金集(golden):关键业务路径,每次合入跑
回归集(regression):全量,发布前跑
flaky 隔离区:疑似 flaky 的用例登记治理,不许带重试混在主集里每次线上 bug 修复后新增的回归用例,归入回归集,逐步织密保护网。
执行反模式
反模式一:只跑单元就宣称测过。 单元快、绿得好看,但权限、协作、构建问题都在更后面的层。对策:分层定义清楚,发布前强制全量。
反模式二:跳过/未执行藏着不报。 报告一个”通过”,把没跑的略过。对策:五类状态强制分类,跳过必须显式列原因和负责人。
反模式三:重试压 flaky。 CI 里加 retries: 3,偶发失败自动重跑变绿,团队再也看不到真问题。对策:禁止自动重试掩盖,flaky 进治理流程。
反模式四:dev 绿就当通过。 静态站 dev 模式和生产构建行为不同,dev 不报错的类型错误、幻觉导入,构建才暴露。对策:构建是独立的强制层。
反模式五:无停止条件。 “差不多全绿”就发布。对策:把停止条件列成硬清单,任一命中即不得准出。
失败回退
- 用例失败且定位为产品/测试/环境/数据问题 → 进入第 12 篇调试流程;
- 发现覆盖空洞(某 REQ 根本没对应用例在跑)→ 回到测试设计 / 自动化补;
- 环境反复失败导致结果不可信 → 先修环境/测试基建,不发布;
- 全量回归通过、证据完整 → 进入第 14 篇 Verify,汇总结论做需求级证明。
人审重点
- AI 负责:按分层执行、收集结果、生成分类报告;
- 人负责:准出决策(能不能发布,人拍板)、确认跳过/未执行是否可接受、高风险失败的跟进、核对执行范围是否真的覆盖了改动;
- AI 可以报告”测试全绿”,但”证据足以发布”这个判断永远在人。
结语
测试执行的产出不是”绿色”,而是一份诚实的证据:跑了什么、什么结果、什么没跑、为什么没跑。绿色可以伪造(跳过、重试、只跑单元),诚实的覆盖状态不能——发布决策应该建立在后者之上。
所有该跑的测试都跑了、结果诚实、证据完整——但这仍然只证明了”测试通过”。测试通过和”需求被真正满足”之间,还隔着最后一道认知。下一篇是验证线的核心:Verify——为什么所有命令变绿依然不代表产品正确,怎么把类型检查、自动化测试、人工验收、Diff 审查、需求追踪矩阵汇总成一份”每条需求都被证明”的结论,以及”已验证 / 未验证 / 无法验证”的边界应该怎么诚实标注。
