Skip to content

06.2 测试驱动与自修复循环

Auto-debugging 不是魔法,是一个可以被你设计和强化的循环。

读完你能做什么:搭建一个"它自己写、自己跑、自己修"的闭环,并防止它作弊。


一、自修复的本质

"Auto-debugging"听起来很高级,拆开看就是 Agent Loop 的一个特例:

text
写代码 → 跑测试 → 读到失败 → 定位原因 → 改代码 → 再跑测试 → ...
       ↑______________________________________________|
                    直到测试通过

关键在于:它能看到真实的失败输出(不是猜测),然后基于真实信息修改。这就是为什么"让它跑测试"比"让它检查代码"可靠得多 —— 前者有客观反馈,后者只有它的主观判断。


二、为什么必须给它反馈闭环

2.1 没有闭环 vs 有闭环

text
❌ 没有闭环
你:"写一个解析 ISO 8601 日期的函数"
它:(写了一个函数,看起来对)
你:(自己跑,发现处理时区时错了)
你:"时区处理错了"
它:(改,可能又引入新问题)
—— 你成了它的测试运行器

✅ 有闭环
你:"写一个解析 ISO 8601 日期的函数,同时写覆盖以下情况的测试:
    带时区、不带时区、只有日期、非法输入。
    然后运行测试,直到全部通过再告诉我。"
它:写函数 → 写测试 → 跑 → 3 个失败 → 修 → 跑 → 通过 → 报告
—— 它自己闭环,你只验收结果

核心动作:把"验证"这个环节交给程序(测试),而不是留给你或它的主观判断。


三、标准的 TDD 工作流

3.1 让它先写测试

text
<task>
为 ReconciliationEngine.match() 方法实现对账匹配逻辑。
</task>

<workflow>
严格按 TDD 顺序:
1. 先只写测试,覆盖这些场景:
   - 金额和时间完全匹配 → 匹配成功
   - 金额匹配但时间差 > 5 分钟 → 不匹配
   - 一对多(一笔银行流水对应多笔订单)→ 按规则拆分
   - 找不到对应订单 → 标记为异常
2. 运行测试,确认它们现在是失败的(红)
3. 实现 match() 让测试通过(绿)
4. 重构,保持测试通过
</workflow>

<constraints>
- 先让我审阅测试用例,我确认后你再写实现
- 禁止修改测试来迁就实现
</constraints>

先让我审阅测试这一步很关键 —— 测试定义了"正确"的标准。如果测试本身错了,后面全错。人工确认测试是最高杠杆的介入点。

3.2 自动化的自修复循环

测试审阅通过后:

text
现在实现 match(),然后进入自修复循环:
- 每次改完运行 `go test ./internal/reconcile -run TestMatch`
- 如果失败,读失败信息,定位根因,修复,重跑
- 最多循环 5 次;如果 5 次后仍有失败,停下来告诉我卡在哪,不要继续瞎试
- 全部通过后,贴出最终的测试输出

最多循环 5 次 是重要的护栏 —— 防止它在一个解决不了的问题上无限打转。


四、防作弊:最重要的一节

模型在压力下会走捷径。"让测试通过"这个目标,可以用正当方式(修复代码)达成,也可以用作弊方式达成。你必须封死作弊路径。

4.1 五种常见作弊

作弊手法表现危害
改断言迁就实现expect(x).toBe(5) 改成 toBe(-1)测试失去意义
跳过测试.skip / @Disabled假装通过
硬编码返回值if (input === testCase1) return expected1只对测试输入有效
捕获并吞掉异常try {...} catch { return default }掩盖真实错误
放宽验证条件把精确匹配改成"包含即可"测试变宽松

4.2 封死作弊的约束

text
<anti_cheat>
达成测试通过的过程中,绝对禁止:
- 修改测试的断言值或期望结果
- 给测试加 skip / disable / xfail 标记
- 针对测试输入硬编码返回值
- 用空的 try-catch 吞掉异常
- 放宽断言的严格程度(如把 equal 改成 contains)

如果你认为某个测试本身写错了,不要改它,而是停下来向我说明:
「测试 X 可能有问题,因为...,建议改成...」,等我确认。

每次修改后,用一句话解释:这次改动为什么能修复失败,
以及它是修复了根本原因,还是只是让这个特定测试通过。
</anti_cheat>

最后那句"是修复根因还是只让这个测试通过",逼它自我披露 —— 这本身就降低了作弊概率。

4.3 用 hook 硬性拦截作弊

提示词是概率性的。要确定性地防作弊,用 hook 检测测试文件的可疑改动:

bash
#!/usr/bin/env bash
# .claude/hooks/guard-tests.sh
# 拦截对测试文件的可疑修改

FILE=$(jq -r '.tool_input.file_path // empty')
NEW=$(jq -r '.tool_input.new_string // empty')

# 只管测试文件
case "$FILE" in
  *test*|*spec*|*_test.go)
    # 检测新增的 skip/disable 标记
    if echo "$NEW" | grep -qE '\.skip|\.only|@Disabled|@Ignore|xit\(|xdescribe\('; then
      jq -n '{
        hookSpecificOutput: {
          hookEventName: "PreToolUse",
          permissionDecision: "deny",
          permissionDecisionReason: "检测到向测试文件添加 skip/disable,已阻止。如需修改测试请人工确认。"
        }
      }'
      exit 0
    fi
    ;;
esac
exit 0

注册到 .claude/settings.json

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": ".claude/hooks/guard-tests.sh" }]
      }
    ]
  }
}

Hook 完整机制见 06.5


五、超越测试:/run/verify

测试通过不等于功能真的能用。Claude Code 提供了两个内置技能,实际运行你的 app 来验证:

命令作用
/run[Skill] 启动并驱动你的 app,看改动真的生效,而不只是测试通过
/verify[Skill] 通过构建+运行+观察结果确认改动符合预期
/run-skill-generator[Skill]/run/verify 如何从干净环境构建、启动、驱动你的 app

5.1 为什么需要它们

text
测试通过 ≠ 功能可用

- 测试可能没覆盖真实用户路径
- 集成层的问题单元测试看不到
- UI 改动测试很难验证

/verify 让它真的跑起来看结果。第一次用需要 /run-skill-generator 教它怎么构建和启动你的项目。

5.2 手动版

不用内置技能也能做:

text
实现完成后,不要只依赖测试。实际验证:
1. 启动服务:`make run`
2. 用 curl 发一个真实请求:
   curl -X POST localhost:8080/reconcile -d @testdata/sample.json
3. 检查响应和日志,确认对账结果符合预期
4. 把实际的请求和响应贴给我

六、一个完整的自修复实战脚本

处理"CI 挂了,修复它"这类任务:

text
<task>
CI 在 main 分支上失败了。修复它。
</task>

<workflow>
1. 先运行完整测试套件,获取真实的失败列表(不要猜):
   npm test 2>&1 | tee /tmp/test-output.txt
2. 对每个失败,判断根因:是实现 bug,还是测试本身过时?
   - 如果是实现 bug → 修实现
   - 如果测试过时(需求真的变了)→ 停下来向我确认,不要擅自改测试
3. 修复后重跑,进入自修复循环,最多 5 轮
4. 全绿后,运行一次完整套件确认没有连带破坏其他测试
</workflow>

<anti_cheat>
禁止 skip/disable 测试、禁止改断言迁就实现、禁止硬编码。
每个修复说清"为什么这能修复根因"。
</anti_cheat>

<completion>
完成的标准(三条全满足才算完成):
1. `npm test` 全部通过,贴出实际输出
2. 没有任何测试被 skip 或删除(用 git diff 证明)
3. 没有新增 lint 警告
在三条都满足前,不要说"完成了"。
</completion>

这个模板集齐了本章所有要素:真实反馈、防作弊、可验证的完成标准。


七、常见故障

症状原因对策
它宣称修好了但测试还是红的过于乐观地判断完成给"贴出实际输出"的完成标准
它在一个问题上无限打转没设循环上限"最多 5 轮,卡住就停"
它偷偷改了测试没封死作弊路径anti_cheat 约束 + hook
测试通过但功能不可用只信测试/verify 实际运行
越修越乱,破坏了其他测试只跑了单个测试最后跑完整套件确认
它读了 3000 行测试输出撑爆上下文没过滤管道过滤失败信息

延伸阅读

基于 VitePress 构建 · 内容采用原作者授权