Home
avatar

.Sam

【AI Native开发-13】“测试通过了”可能是最贵的一句谎话

30 秒看懂这篇

“测试通过了”可能是本次开发里最贵的一句谎话:它也许只跑了三个单元测试,却让你以为整套系统都安全。

真正难的不是把测试跑起来,而是同时回答两个问题:测到什么深度?这次该跑多大范围? 很多人把它们混成一座“测试金字塔”,结果不是反馈太慢,就是覆盖出现黑洞。

flowchart TB
    subgraph G["维度一:测试粒度——验证什么"]
        U["单元:纯逻辑"] --> I["接口 / 集成:真实协作"] --> E["E2E:关键用户路径"]
    end
    subgraph S["维度二:执行策略——这次跑多少"]
        M["Smoke:核心路径"] --> A["Affected:改动影响面"] --> F["Full:完整回归"]
    end
    G -. "每个批次都可组合不同粒度" .-> S

这套体系有三条底线:

  1. 粒度与批次分开设计。 单元、接口、E2E 解决不同问题;smoke、affected、full 决定执行范围。
  2. 结果必须分类汇总。 通过、失败、跳过、未执行、阻塞,一个都不能藏进“通过”。
  3. 停止条件先于发布欲望。 环境不稳、证据不足或高风险失败未定位时,结论只能是“不准出”。

本篇给出二维执行模型、统一报告、并行隔离和停止条件,让“跑了测试”变成可核对的发布证据。

一个典型场景

删除功能做完了,同事问:“测了吗?“你让 AI”跑一下测试”。它跑了一会儿回:“通过了。”

你准备发布,多问了一句:“都跑了哪些?“它答:“删除相关的单元测试跑了,都过了。”

再追问发现:

  • 接口测试根本没跑(AI 觉得单元测试过了就行);
  • 两个 E2E 用例因为浏览器环境没起,被跳过了,但没说;
  • 有一个测试标记了 skip(之前调试 flaky 留的),一直没恢复;
  • 类型检查和构建没跑——而上次正是生产构建才暴露了 article-loader 的类型错误。

“通过了”这三个字,掩盖了大片根本没执行的验证。测试执行的价值不在于”跑了”,而在于”跑了该跑的、结果可核对、没跑的被显式知道”。

核心问题:为什么”跑测试”需要被设计

AI 执行测试有几个默认问题:

  • 挑容易的跑:单元测试快、好跑,就只跑单元;E2E 慢、要起环境,就默认跳过;
  • 报喜不报忧:倾向于报告”通过”,跳过的、没跑的、环境起不来的,一笔带过;
  • 不区分批次:要么只跑一两个,要么全跑(全跑太慢就干脆不跑);
  • 结果不可复现:这次跑这些、那次跑那些,没法回答”这个版本到底验证了什么”。

所以测试执行要像流水线一样被设计:什么时机跑哪一层、结果怎么记、什么情况必须停,都要有明确规则,而不是靠 AI 临场判断。

两个维度:粒度决定深度,批次决定范围

测试粒度回答“这条测试能证明什么”:

粒度主要证据典型反馈速度不能替代什么
单元纯规则与边界计算毫秒到秒级真实路由、中间件与浏览器协作
接口 / 集成模块间真实协作与副作用秒级完整用户操作和视觉表现
E2E关键用户旅程秒到分钟级所有细粒度边界组合

执行策略回答“这次从测试资产里选多少”:

  1. Smoke:核心路径。 个位数用例,部署后快速判断系统是否基本可用。
  2. Affected:改动影响面。 根据 Diff、依赖关系和 REQ→TEST 映射选择相关单元、接口与必要的 E2E;不能只按目录名猜。
  3. Full:完整回归。 全部计划的测试,加类型检查与构建,用于功能完成、合入和发布前的风险降低。

例如,一次 affected 批次可以同时包含单元和接口测试;smoke 里也可以有一条 E2E。Smoke / affected / full 不是单元 / 接口 / E2E 的别名。

纪律:低粒度不能替代高粒度,完整回归也不能证明“绝无回归”——它只能在既有用例、环境和证据边界内降低风险。发布前应执行团队约定的 full 范围,未执行项必须进入报告。

执行顺序:从快到慢,从便宜到昂贵

一次完整验证,按这个顺序跑,让便宜的检查先拦截问题:

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   ← 测试资产中显式跳过,必须列出原因
- 未执行(not run):8 ← 本批次未选择执行,必须说明范围依据
- 阻塞(blocked):1   ← 计划执行,但被环境 / fixture 故障阻断

失败明细:
- TEST-002-1 普通用户删除:失败,期望 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 治理区:登记首次失败、频率、负责人和恢复期限;
              若启用 retry,flaky 必须单列,不得混入稳定通过数

可自动化的线上 Bug 修复后新增 CI 回归用例;不能稳定自动化的,进入人工验收、监控或运行手册。

执行反模式

反模式一:只跑单元就宣称测过。 单元快、绿得好看,但权限、协作、构建问题都在更后面的层。对策:粒度与批次分开定义,发布前执行约定的 full 范围。

反模式二:跳过/未执行藏着不报。 报告一个”通过”,把没跑的略过。对策:五类状态强制分类,跳过必须显式列原因和负责人。

反模式三:重试压 flaky。 CI 里加 retries: 3 后只展示最终绿色,团队再也看不到真问题。对策:保留首次失败,单独报告 flaky,retry 不能替代根因治理。

反模式四:dev 绿就当通过。 静态站 dev 模式和生产构建行为不同,dev 不报错的类型错误、幻觉导入,构建才暴露。对策:构建是独立的强制层。

反模式五:无停止条件。 “差不多全绿”就发布。对策:把停止条件列成硬清单,任一命中即不得准出。

回退与准出

失败进入第 12 篇调试,覆盖空洞回测试设计或自动化,环境不可信则停止发布;证据完整后进入第 14 篇 Verify。AI 可以执行并汇总结果,人核对范围、跳过与阻塞项,并作准出决策。

结语

测试执行的产出不是”绿色”,而是一份诚实的证据:跑了什么、什么结果、什么没跑、为什么没跑。绿色可以伪造(跳过、重试、只跑单元),诚实的覆盖状态不能——发布决策应该建立在后者之上。


所有该跑的测试都跑了、结果诚实、证据完整——但这仍然只证明了”测试通过”。测试通过和”需求被真正满足”之间,还隔着最后一道认知。下一篇是验证线的核心:Verify——为什么所有命令变绿依然不代表产品正确,怎么把类型检查、自动化测试、人工验收、Diff 审查、需求追踪矩阵汇总成一份”每条需求都被证明”的结论,以及”已验证 / 未验证 / 无法验证”的边界应该怎么诚实标注。

AI Native SDD 测试执行 质量流水线 CI 测试工程