AI 生成的测试往往复制实现代码中的字面量,只验证「生成的故事」而非外部契约。建议要求在 diff 之前先写 invariant,用独立测试预言机验证外部行为。
AI 写的测试,依然在回声补丁
通过测试并不能证明 AI 理解了契约。它往往只能证明 AI 引用了它自己生成的补丁。这个差距才是合并风险所在,而非模型本身。
我持续在审查那些看起来工程完备的 Pull Request。行文冷静,Checklist 绿色。然而为什么生产环境依然让值班工程师措手不及?
我们将聊天驱动的补丁洗白成了审查剧场。流畅成了证据,回声成了假先知。本文列举了我持续看到的四种反模式。
每个条目都有症状、根本原因和替代方案。我也包含了一个可拷贝的先知检查(oracle check)和一个命令锚点(command pin)。把这些片段当作标记示例,而非生产历史。
这不是对当前编程模型的排名,也不是在说 AI 不能写代码。这是一套你明天就能运行的审查工作流。
我不会发布时序、胜者或客户案例。那些数据会过时,而我不会编造。如果你需要带签名的容量数据,用你自己的实验室。
摘要读起来像是一位高级工程师写的每一行代码。
审查者称赞语气、结构以及看似清晰的推理。
没人能说出这里实际改变的到底是什么不变量(invariant)。
流畅是廉价的,而真正的不变量是昂贵的。AI 可以叙述一个补丁而不拥有它的失败。你的流程随后把叙述当成了真正的技术审查。
在任何人在打开 diff 之前,要求写出一个不变量。如果作者无法陈述它,PR 仍然是一个 spike。Spike 需要一张 ticket、一个日期,但没有合并按钮。
INVARIANT: refunds never exceed the captured amount
PROOF: test_refund_cap in tests/billing_oracle.py
OWNER: @oncall-owner
在每次 AI 参与的审查中问一个尖锐的问题。如果我们现在删除生成的注释,什么仍然会失败?如果答案是"什么都没有",你审的是一篇博客文章。
测试与实现来自同一个对话。
断言重复了从生成主体复制的字面量。
重命名一个常量会让测试在实际生产之前就失败。
AI 测试的是它自己的故事,而非外部契约。测试文件是一面镜子,位于补丁旁边。绿色只意味着我再次同意了我自己。
测试在实际实现之前就存在过吗?如果答案是否定的,你买的可能是一个回声(echo),不是一个先知(oracle)。先知来自规格说明(spec)。回声来自 stdout。
在实现之前先写好先知。从规格说明中固定预期值,而非从对话输出中。如果你缺少规格说明,你仍然缺少真正的测试。
这是一个有标签的示例,不是测量套件。
# example_oracle.py — proposal, not a production run
from decimal import Decimal
SPEC_CAPTURED = Decimal("40.00")
SPEC_REQUESTED = Decimal("99.99")
SPEC_REFUND = Decimal("40.00") # from the billing spec, not stdout
def refund_cap(captured: Decimal, requested: Decimal) -> Decimal:
if requested < 0:
raise ValueError("requested must be >= 0")
return min(captured, requested)
def test_refund_never_exceeds_capture():
assert refund_cap(SPEC_CAPTURED, SPEC_REQUESTED) == SPEC_REFUND
def test_oracle_does_not_quote_the_function():
source = open(__file__, encoding="utf-8").read()
banned = "assert refund_cap(SPEC_CAPTURED, SPEC_REQUESTED) == refund_cap"
assert banned not in source
用无聊的、可重复的命令运行这个示例。
python -m pytest example_oracle.py -q
如果唯一的断言是"输出等于输出",停下来。那是一个引用(quote),不是契约。
如果需要,在 PR 模板中打印那张表。我宁愿看到一行填满的数据,也不愿看到十个形容词。你的最后一个 AI 补丁实际属于哪一列?
对话日志比最终 diff 更长。
又一轮礼貌的重试之后,失败消失了。
没人保存第一个真正失败的命令。
你优化了 prompt 而非固定系统。失败从未变成哈希的、可重复的命令。下一个模型,或第二天早上,会复活它。
修复是新行为,还是新的鼓励(pep talk)?如果你无法判断,你没有修复。你有一个带着绿色徽章的情绪波动。
固定失败的命令,而不是激励线程。在每次 prompt 更改后重新运行那个确切的命令。如果命令漂移了,所谓的修复只是剧场。
# pin the failure; do not paraphrase it in chat
cat > /tmp/failing_cmd.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
python -m pytest tests/billing_oracle.py -q
EOF
chmod +x /tmp/failing_cmd.sh
sha256sum /tmp/failing_cmd.sh
/tmp/failing_cmd.sh; echo EXIT:$?
将哈希值粘贴到 Pull Request 正文中。这个命令改变了吗,还是行为改变了?只有第二个答案才算是真正的修复。
当检查重要时,将脚本保留在仓库中。聊单中的 gist 会在下一次值班轮换前腐烂。CI 应该运行与你的笔记本相同的字节。
install -d scripts
cp /tmp/failing_cmd.sh scripts/oracle_refund.sh
git add scripts/oracle_refund.sh tests/billing_oracle.py
git commit -m "pin refund oracle and the failing command"
它在免费环境中运行过,所以我们称之为完成。
没有 owner、没有 data class、没有 rollback、没有 page path。
演示路径是任何人执行的唯一路径。
一次性环境只证明一件事:可复现性。它不能证明容量、身份或爆炸半径(blast radius)。人们合并这些证明,因为演示感觉良好。
你会在凌晨三点呼叫这个沙箱吗?如果答案是否定的,它从未签署过生产环境。停止让干净的演示为未签名变更洗白。
分割检查。保持它们无聊且明确命名。
我只在复现框中使用免费编程环境。MonkeyCode 是一个开源选项,提供免费模型访问和免费服务器。披露:本文是 MonkeyCode 产品推广的一部分。
那个盒子可以从干净的树重新运行 scripts/oracle_refund.sh。它不能签署你的事故、你的身份或客户数据。如果你需要这些签名,你已经离开了沙箱。
在下一个 AI 驱动的 Pull Request 上执行此操作。不要跳过书写。书写就是审查。空字段意味着你仍在聊天驱动模式中。
用一句简单的话陈述不变量。
添加一个从不导入补丁字面量的先知测试。
固定失败的命令并存储其 sha256。
将沙箱结果标记为已复现(reproduced),而非已交付(shipped)。
指定一个可以在没有聊天日志的情况下回滚的 owner。
PR template
- Invariant:
- Oracle test path:
- Failing command hash:
- Reproduced on (sandbox/CI):
- Ship check (yes/no + owner):
在公开评论中填写,而不是在私人侧栏中填写。如果任何字段为空,合并仍然是 spike。称之为 spike。不要称之为工程。
想在人类审查之前进行一个小检查吗?这个钩子只检查模板字段是否存在。它不会证明先知是明智的。没有什么本地可以做到。
# scripts/check_pr_template.sh — proposal
set -euo pipefail
file="${1:-pr_body.md}"
for key in Invariant "Oracle test path" "Failing command hash" "Ship check"; do
grep -q "^- ${key}:" "$file"
grep -vq "^- ${key}: *$" "$file"
done
不要将本目录作为禁止模型的理由。不要使用免费服务器作为负载或浸泡测试。不要在那里粘贴 secrets、prod dumps 或客户记录。
也不要将我的示例当作测量基准。我没有命名模型、配额、硬件或持续时间。那些数字会腐烂。这些反模式不会。
当变更已经明确规范时跳过这篇说教。带有 golden tests 的 owned RFC 不需要这个仪式。你已经有一个先知。继续在 CI 中喂养它。
对于你今天会删除的一次性 spike 也要跳过。只是不要因为行文听起来确定就合并它。听起来确定是整个技术栈中最便宜的部分。
简短的审查问题不会捕获每种失败模式。当规范本身错误时,先知也会出错。如果没有人运行它们,固定的命令会腐烂。
这个工作流也会减慢真正的探索性 spike。这个减速就是重点,而不是流程的事故。如果减速造成伤害,你就是在发布叙述。
我不能从博客示例中证明你的账单规则。你领域的规范不变量必须来自你自己的规范。复制方法。不要盲目复制我的退款数字。
我想要一个陌生人以后可以证伪的句子。我想要一个在规范改变时会失败的测试。我想要一个不依赖魅力的命令哈希。
你审查的是行为,还是只审查了语气?测试是引用了补丁,还是固定了契约?如果你无法判断,AI 没有失败。审查失败了。
固定命令。指定 owner。然后考虑合并。这就是全部方法,而且它仍然适合一个模板。AI 可以起草。它仍然不能签署事故。