---
name: better-test
description: 写、改、审查或清理测试时使用：价值门、反模式、见红、接缝与 mock 边界、DELETE/REWRITE/KEEP 判决，以及自动化够不到时的取证。
---

# Better Test

测试是负债：每条持久测试都必须在失败时说明某个真实行为坏了。覆盖率、测试数量、"每个函数一条"都不是目标。

只走一个分支：

1. 写或改测试 → **写测试**。
2. 审计或清理既有测试 → **清理与审计**。
3. 修已知 bug → 先在 bug 真实发生的接缝上写出复现用户准确症状的失败用例，再修；这条回归用例按**写测试**过价值门。
4. 行为值得保护但自动化碰不到 → **取证**。

## 价值门

写新测试与判既有测试共用这一道门，六条必须全部成立：

1. **有来源**：保护的行为有独立依据——需求、bug、公开合同、可访问性规则或规格示例。
2. **失败有意义**：一种可信回归会让它失败，且失败对应调用方或用户可见的问题。
3. **期望值独立**：期望值来自已知字面值、规格或示例，有可能与实现结果不一致。
4. **稳定接缝**：经公开接口或稳定 seam 观察，不依赖只为测试存在的生产导出、flag、wrapper 或注入点。
5. **抗重构**：行为不变的内部重构、无关文案或布局调整后仍然通过。
6. **唯一 owner**：该合同只在最低的可靠边界证明一次，且没有被现有测试、类型系统、编译器或 lint 等价保证。

**接缝**是不深入内部就能观察行为的公共边界。纯规则放领域测试，编排与授权结果放应用层，交互合同放组件测试，端到端只留关键用户流程。示例见 [references/tests.md](./references/tests.md)，mock 边界见 [references/mocking.md](./references/mocking.md)。

## 反模式

判断时按类对照；正则或文本命中只是线索，读完测试和被测代码才下结论。

- **同义反复**：按实现的算法重算期望值；断言声明等于自身；mock 返回什么就断言什么；期望值由被测 helper 或 renderer 自己产出。
- **变更探测器**：扫源码、import、标识符或字符串；数导出、路由、元素或清单行数；快照生成的 markup、组件树或 class 串；断言私有协作者、调用次数或调用形状。
- **重复**：同一场景在它穿过的每一层都重放一遍；近乎相同的用例（合并成表驱动）；已被类型或编译保证的检查。
- **空洞**：没有断言或只证明"没崩"；只测 getter、常量或框架能渲染。
- **虚假信号**：负向对照因无关原因通过（另一个守卫先拒绝、生产路径根本到不了）；fixture 替被测方产出本该由它产生的结果；测试名承诺的比输入实际覆盖的多。
- **测试专用生产接缝**：只有测试调用的导出、全局、wrapper、flag，以及只被测试调用的死代码。
- **外观当行为**：尺寸、比例、坐标、computed style、布局 class、`scrollWidth`、挂载行数是外观证据，不是行为。视口只提供场景，断言落在用户任务完成上（移动端能找到并操作某个按钮）。用 role、可访问名、label 定位控件；只有措辞本身是需求时才断言文案。焦点和禁用态只在它就是交互合同时测（聚焦首个无效字段、对话框关闭后焦点归还）；归一化矩阵放纯函数测试，组件层留一条代表用例。截图断言只在项目明确采用视觉回归时入套件。

**像实现但必须保留**：公开 API、协议与序列化字节、配置、迁移、存储格式、安全、默认值、跨语言生成物、架构约束——它们本身就是独立合同，满足价值门第 1 条。顺序可被观察时的调用顺序、以最低成本守住用户可见键名或路径的源码检查同理。

## 写测试

1. **先决定要不要写**：纯重构、改名、格式化等无行为变化 → 只跑受影响的既有验证。已有用例覆盖该行为 → 改它，不新增。新行为 → 过价值门，不过就不写，也不记测试债。
2. **放在 owner 边界**：同一行为的多处改动共用一条用例；优先扩展表驱动用例或共享 fixture。需要测试专用生产接缝时，换到真实边界上测。
3. **见红**：每条新测试都要亲眼看到它因预期原因失败一次。验收测试（implement）和 bug 回归测试天然先红；为既有行为补的测试，临时破坏被测实现（不是改断言），运行确认变红，再精确还原并跑绿。从没红过的测试只证明了 mock。
4. **写法**：一条用例一个行为，失败信息能直接定位合同；固定 seed、时钟和网络，用条件等待代替 sleep；不留永久 skip、临时断言、僵尸进程或生成物。
5. **顺手清理**：本次触及的测试文件及同一行为的相邻用例，按下文判决规则直接处理，不另请授权。
6. **验证**：跑最窄受影响验证。

完成标准：每条新增或保留的触及用例都过价值门且见过红；报告里有命令、结果，以及删除或拒绝新增的理由。

## 清理与审计

**范围与授权**：范围按用户请求钉死——diff、路径、模块或全仓；全仓只在用户明确要求时做。"审计""看看""找找"是只读，交报告为止；"清理""删掉""精简"授权在范围内直接改。

**分工**：每条判决要读的东西多，范围跨 3 个以上模块时按 [fan-out](../fan-out/SKILL.md) 中档分两波；全仓清理或用户要求仔细时用高档。

- 按生产 owner 模块切片，每片带该模块的测试文件。多个模块共用的支撑代码（fixture、helper、全局 setup）不分给工人，由编排者最后处理，保持每个文件一个写者。
- 第一波只读：与编排者同级的实现角色逐条判决，格式同下文报告，另交**跨片线索**——疑似与别片重复证明的合同（测试名、合同一句话、位置），没有写"无"。
- 收口：同一合同跨片重复时保留最低可靠边界上的那条，其余改判 DELETE 并补证据；抽查每片的 KEEP 举证和 DELETE 证据，不全的退回。高档时 DELETE 另派不同模型的子代理独立复判，两份一致才删，不一致记为未定。审计到此合并报告。
- 第二波（清理）：原工人（或附定稿判决的新工人）只改片内文件并跑片内最窄验证；编排者最后删共享支撑代码和跨片的测试专用接缝，跑受影响验证。

**逐条判决**，不按文件。判之前读完测试全文、被测生产 owner、它的调用方、重叠测试、相关 git 历史，以及项目自己的测试规则。

- **KEEP**：价值门六条逐条举证全部成立。
- **REWRITE**：测试里有一个过门的合同，但现写法不过。只保留证明该合同所需的准备、动作和断言，不沿用旧测试的体量、断言数或层级。
- **DELETE**：不过门，且写全三项证据——它实际能抓住什么失败；该行为别处更强的证明，或为什么不需要证明；它或它依赖的测试专用接缝当初为何存在。
- **未定**：KEEP 举证不全，DELETE 证据也不全。写进报告，不动。

既有测试在基线上就失败时，当作可能的产品缺陷先定位根因，不删。

**执行**：删测试时一并删掉它独占的测试专用生产接缝与死代码，以及因此失效的 fixture、helper、快照和 setup。为保住测试而改生产行为、降低断言、关闭规则、调低覆盖率阈值都不是解法；覆盖率或数量门槛挡住合理删除时，报告冲突。改完跑最窄受影响验证和仓库要求的门。

**报告**：写明是审计建议还是已完成的清理。逐条列出 DELETE / REWRITE / KEEP / 未定 的位置与理由；KEEP 和 REWRITE 附六条举证，DELETE 附三项证据；列出删掉的生产接缝、跑过的命令与结果，生产代码与测试代码的行数变化分开写。

完成标准：范围内每个候选都有判决，留下的测试全部过门，没有残留的死测试支撑代码，验证结果已知。

## 取证

按接近用户真实路径的顺序选最短方法：

1. **better-browser-use**：页面、DOM、截图、console、network；
2. curl 或 CLI：直接调用并断言响应内容；
3. 带具体断言的日志；
4. **better-computer-use**：桌面应用或真机兜底，读取控件并截图；
5. 都不可行：给用户"打开哪里 → 做什么 → 应看到什么"的最短清单，标注未验证。

证据必须来自本次执行。只有过价值门、值得长期保护的缺口才记测试债；同一路径第二次需要人工取证时，向用户提出补一个自动化验证入口。
