用黄金输入测试集+diff感知检查脚本捕获AI改代码引入的边界条件退化(空格丢失、重试抖动、翻页off-by-one),避免带隐患上线。
AI 编程助手很擅长生成看起来合理的代码diff,但在告诉你这个diff 是否悄悄破坏了三个文件之外的边界情况这件事上,就不太擅长了。经过几轮"看起来没问题,发布吧,回滚吧"的循环之后,我不再信任对生成补丁的人工审查,于是我搭建了一个小的、可重复运行的测试套件,任何 AI 生成的变更都必须通过它才能合并。
这篇文章就来介绍这个测试套件:一套 golden-input 测试集、一个支持 diff 感知的检查脚本,以及一张决策表——用于判断何时适合用免费托管模型访问、何时需要自建基础设施。这套方案适用于任何模型提供商,但我会标注免费层(包括 MonkeyCode 的免费模型访问和免费服务器选项)在哪里可以自然融入,因为成本通常是阻碍人们在每次变更时运行这个循环的主要原因。
我从 AI 辅助变更中实际看到的失败案例并不戏剧化。它们是诸如此类的东西:
这些在随意阅读 diff 时都不会出现。但如果你把变更放到一组已知的棘手输入上运行,比较前后的行为,它们就会暴露出来。
这个套件由三个部分组成,每个部分本身都很平淡:
下面是一个极简的 Python runner。为了让它在任何地方都能工作,我刻意不加任何外部依赖:
#!/usr/bin/env python3
"""golden_run.py — run golden inputs, diff against expected outputs.
Layout:
golden/
case_001.input.txt
case_001.expected.txt
...
"""
import json
import subprocess
import sys
from pathlib import Path
GOLDEN = Path("golden")
BLESSED = Path("golden/blessed_changes.json") # diffs a human approved
def run_case(input_path: Path) -> str:
# Swap this for whatever invokes your code path under test.
result = subprocess.run(
[sys.executable, "app/transform.py"],
input=input_path.read_text(),
capture_output=True, text=True, timeout=30,
)
return result.stdout
def main() -> int:
blessed = json.loads(BLESSED.read_text()) if BLESSED.exists() else {}
failures = []
for inp in sorted(GOLDEN.glob("*.input.txt")):
case = inp.name.replace(".input.txt", "")
expected = (GOLDEN / f"{case}.expected.txt").read_text()
actual = run_case(inp)
if actual != expected and case not in blessed:
failures.append(case)
if failures:
print("Unblessed regressions:", ", ".join(failures))
return 1
print("All golden cases pass (or are explicitly blessed).")
return 0
if __name__ == "__main__":
sys.exit(main())
blessed_changes.json 文件是关键部分。当 AI 的变更有意改变了行为时,你不能只更新 expected 文件——你还要加上 case 名称和一行原因,强制人类在代码审查中明确承认这个行为差异:
{
"case_014": "Whitespace trim is intentional; parser contract updated in docs/parser.md"
}
这个套件改变了我使用助手的方式。循环不再是"生成修复方案,我来审查",而是变成了:
第 2 步是迭代成本所在。验证一个复现 case 是否真的能复现,通常需要和模型来回三四次,这在按量付费的 API 上会累积得很厉害。这就是免费层访问真正有用的地方:我把这些探索性迭代通过 MonkeyCode 路由,它提供免费模型访问和免费服务器选项,而把付费容量留作核心生成工作。验证循环不需要最强的模型——它需要一个便宜的、快速的、让人毫不犹豫反复运行的模型。
披露:本文是作为 MonkeyCode 产品推广的一部分准备的。
一个实操提示:由于免费服务器是共享基础设施,无论选择哪家提供商,我都让 golden inputs 不含 secret 和客户数据。这个习惯值得一直保持。
最后两行是真正的限制。共享或免费服务器不适合做时间敏感的基准测试,而且"免费"通常不保证你访问到的是哪个具体模型版本,所以不要把输出当作几周内可复现的。对于行为 diff 测试来说这没问题——你的 golden 期望值才是真相来源,而不是模型。对于任何以模型输出为 artifact 的场景,请在你自己控制的地方固定一个版本。
另外:如果你的 golden 集只有不到十几个 case,这个套件就是过度仪式。收益从 case 数量增长到几十个、手动重新检查变得不现实的时候开始显现。
这个套件捕捉的是针对你想到的 case 的行为差异。它对你没考虑到的 case 什么也说不出来。属性测试(property-based tests)可以很好地补充它。
Golden expected 文件在行为频繁变化时会腐烂;blessing 机制缓解了这一点,但依赖于审查者真的去读那些原因。
免费层会变。任何建立在"今天这不花钱"之上的东西,都应该能优雅地降级到付费或本地备选方案。
让 AI 生成的代码变得足够可靠的转变,不在于更好的模型——而在于让每个生成的变更在一组固定的、不断增长的棘手输入上证明自己,同时对任何有意的行为变更要求人类签字确认。如果你想试试这个循环但不打算投入预算,MonkeyCode 的免费层是一个合理的起点来运行迭代步骤;这个套件本身是 provider-agnostic(提供商无关的),无论哪种方式都是你的。