让测试可信
测试通过,不一定说明功能真的做对了。测试可能从没抓到过错误,也可能在实现过程中被改松,甚至被删掉几条后照样全绿。
要让测试成为可信的验收证据,按一条主线做:先证明它真会失败,开工前留住这一版,做完后再比较有没有被改松。多数项目在合并前检查就够;要求 Agent 当场改不动测试时,还要增加更强的写入控制。
13.1 测试的敏感性
新功能和 bug 适合先看红,再写实现:
- 先写对应验收条件的测试。
- 运行测试,确认它因为目标行为还没实现,或者 bug 仍然存在而失败。
- 保存这次运行的命令和关键报错,再写实现让它变绿。
红了还不够,必须看失败原因。测试因为导入错误、数据库没启动或测试代码自己崩了而变红,都没有证明它盯住了目标行为。
重构的起点不同。特征测试记录的是现有行为,所以它在旧实现上应该先通过。要证明它不是”永远为绿”,可以临时改坏一个相关判断或返回值,看到测试变红后立即恢复。已有运行记录已经证明它能抓住同类错误时,不必为了走形式再改坏一次。
文案、配置或依赖更新不一定适合写自动化测试。这时换成别的检查,仍然遵守”先不通过,后通过”:例如一条失败的校验命令、一张不符合预期的快照,或一个写清楚了判断标准的人工观察点。
spec-kit 可以按顺序执行这些任务,但不会默认替项目生成测试。只有规格明确要求测试时,官方任务模板才会加入测试任务。项目仍要检查 tasks.md 里有没有需要的测试,以及测试任务是否排在实现之前。
通过标准:新功能和 bug 留下目标原因导致的失败结果;既有行为先在旧实现上通过,并有证据表明相关行为被破坏时它会失败;其他类型的变更有对应的先失败、后通过记录。
13.2 开工前的基线
测试证明有效后、实现开始前,把这一版留住。后面如果测试变了,才能看出改了什么。
轻 spec、完整流程和高风险任务要留下正式基线。自动化测试可以单独提交,也可以放在临时分支、补丁或版本化快照里。关键不是采用哪种形式,而是之后能取回原文并逐行比较。只记一个校验和,只能知道文件变过,不能看出哪条断言变了,不够当基线。
没有自动化测试时,留下对应的检查标准:校验命令要带完整参数和预期状态,快照要保存原始内容,人工观察要写清看到什么才算通过。
还要分清两件事:存下一个版本,不等于这个版本已经获批。版本记录只回答”以后和哪一版比”;批准记录还要写清谁在什么时候,根据哪条验收条件批准了它。例如:
验收条件:AC-3
基线版本:<commit-snapshot-or-record-id>
批准人:<name>
批准时间:<timestamp>
基线后来变了,就要说明原因并重新批准,不能把旧批准直接搬过去。
直接做的低风险任务可以轻一些:保留前后的检查结果,完成时查看测试或其他验收检查的变化。如果变化已经说不清,补建正式基线,必要时重新判档。
spec-kit 的 implement 不会替项目保存基线、记录批准或单独提交测试,这些要由项目自己补上。
通过标准:需要正式基线的任务能取回开工前的检查内容,并找到对应的批准人、验收条件和时间;直接做的低风险任务至少留下前后结果和检查变化。
13.3 实现后的基线比较
实现完成后,把当前检查和开工前的基线放在一起看。自动化测试可以直接比较文件:
git diff <基线提交> -- <测试路径>
重点看四类变化:断言有没有被删,阈值有没有调松,输入范围有没有缩小,用例有没有被跳过。测试当然可以改,但要说清改动前后各允许什么、拒绝什么。需求真的变了,就先改验收条件,再更新基线并重新批准;不能因为实现过不了,就把测试改到能过。
测试数量也值得看,但它只负责报警,不能直接判断质量。合并重复测试、改成参数化测试或重命名,都可能让数量下降;删掉有效测试再补几条空测试,也能让总数不变。有稳定测试 ID 时,优先比较哪些测试新增、删除、改名或跳过;只有数量时,发现下降就先停下来解释。
其他检查用同样的方法比较。校验命令换了参数、允许了新的退出状态,快照内容变了,或者人工判断标准放宽了,都要被看见。不能在实现失败后偷偷换一条更容易通过的检查。
最后,把每条新增或变更的验收条件反查到测试或其他检查及其结果。旧测试全绿,只能说明旧行为没有被它们发现破坏,不能证明新需求已经做完。
具体怎样把这些检查接进 CI,放到后面的评审与闸门章节。本章先确定要比较什么,以及哪些变化必须停下来处理。
通过标准:当前检查已经和基线比较;删除、跳过、放松阈值等变化都有明确处置;测试数量下降时没有直接放行;每条新增或变更的验收条件都能找到对应的检查结果。
13.4 执行时的写入控制
前面的做法能在合并前发现测试被改,却不能保证 Agent 在本地实现时完全碰不到测试文件。spec-kit 管的是任务顺序,不是文件写入权限。
多数项目不必追求”当场改不动”。允许工作区里的测试发生变化,但要求合并前比较基线、解释差异并重新批准,通常已经够用。把这些动作列为合并前必检项,等配置 CI 和评审门时再验证它们不能被绕过。
只有风险高到不能接受事后发现时,才需要执行时阻断。先写清要保护哪些路径、哪些进程可以写、谁能临时解锁,再选择控制方式:
- 提示词和任务规则只能提醒 Agent,不会阻止写入。
- hook 或编排器可以拦住已经登记的文件工具,但未必拦得住 shell、子进程和其他入口。
- 沙箱、独立工作区或只读挂载可以从系统边界限制写入,但仍要按项目实际开放的入口逐项测试。
因此,不能因为某个方案用了”冻结”或”锁定”这样的名字,就认定测试已经改不动。真正的硬阻断必须说明保护边界,并拿绕过测试证明它确实拦住了项目允许的写入方式。
通过标准:项目已经选择”合并前发现”或”执行时阻断”。选择前者时,基线比较和批准检查已经列入后续闸门;选择后者时,保护路径、可写进程和解锁流程已经写清,并完成实际的绕过测试。
本章小结
可信的验收证据要过三关:先证明检查真能发现问题,开工前留住标准,做完后确认标准没有被偷偷改松。轻 spec、完整流程和高风险任务保存正式基线与批准记录;直接做的低风险任务至少留下前后结果和检查变化。
spec-kit 能组织任务,但不会替项目保存测试基线,也不会禁止 Agent 写测试文件。多数项目用合并前检查兜底;只有确实不能接受事后发现时,才需要自建执行时写入控制。
参考与出处
- 新增行为按测试先行、实现通过、再重构的顺序推进:Martin Fowler: Test Driven Development
- 自动化测试作为持续运行的缺陷探测器:Martin Fowler: Self Testing Code
- 故意改变被测代码、观察测试是否捕获变化的方法:Wikipedia: Mutation testing
- spec-kit 的测试任务为可选项,加入后要求先于实现执行:Spec Kit tasks template
- spec-kit 按
tasks.md执行任务,但未提供测试基线或批准记录步骤:Spec Kit implement command