核心问题从「能否修 bug」转向「修 bug 的同时是否破坏其他功能」,提出用测试套件量化回归率的评估方法。
大多数关于免费编程模型的讨论都在问错误的问题。人们问"它能修复我的 bug 吗?",而更昂贵的实际问题是:当它修复了这个 bug,它还破坏了其他什么?
一个通过了你指向的那个测试、却悄悄让其他三个测试挂掉的 patch,比不修还差——它把一个已知的失败变成了一个未知的失败。根据我审阅 AI 生成 diff 的经验,回归风险恰恰是便宜模型和前沿模型差距最大的地方。不是在它们能否生成一个看似合理的修复,而是在修复是否尊重了代码库的其他部分。
所以我搭建了一个小小的测试框架,来回答每个模型一个可量化的问题:它生成的看起来可用的 patch 中,有多大比例会在现有测试套件中引入回归?这个帖子就是那个测试框架、我和结果一起使用的决策表,以及对整个方法在哪里失效的诚实审视。
ingredients 故意选得很无聊:
一个拥有真实测试套件的仓库。测试套件中与你要修的 bug 无关的部分越多越好——那些是你的绊线。
针对同一个 bug 生成的若干候选 patch,由你要评估的模型产生。我生成多个候选而不是一个,因为单次尝试几乎什么都告诉不了你。
一个隔离的环境来运行所有东西,因为你要用模型生成的代码对着测试套件跑很多次。
关于最后一点:我在一个一次性云服务器上运行这个框架,而不是我的笔记本,部分是因为模型生成的代码永远不会碰到我的工作机器,部分是因为我可以放手让数小时的运行继续而不去管它。这一轮我用了 MonkeyCode,它目前提供免费模型访问(方便生成候选 patch 而不用精打细算提示词)和免费服务器选项(作为测试框架运行的牺牲性沙箱)。披露:本文是 MonkeyCode 产品推广的一部分。不过这个框架完全不依赖该提供商——任何你能 SSH 进去的机器和任何模型端点都能做同样的事。
核心技巧是 git worktree。每个候选 patch 会被应用到它自己的工作树,完整测试套件在那里运行,然后将结果与 base commit 上的测试套件行为进行比较。回归是指在 base 上通过、但在 patch 后失败的任何测试。
#!/usr/bin/env bash
# regress_check.sh <base_commit> <patch_file> <test_command>
# Example: ./regress_check.sh HEAD~1 candidate_03.patch "pytest -x -q"
set -u
BASE="$1"
PATCH="$2"
TEST_CMD="$3"
WT=$(mktemp -d)/wt
git worktree add --detach "$WT" "$BASE" >/dev/null 2>&1
# Baseline: which tests pass before the patch?
( cd "$WT" && eval "$TEST_CMD" --tb=no -q 2>/dev/null | tail -n 1 ) > "$WT.baseline.txt"
if git -C "$WT" apply "$PATCH" 2>/dev/null; then
( cd "$WT" && eval "$TEST_CMD" --tb=no -q 2>/dev/null | tail -n 1 ) > "$WT.patched.txt"
echo "=== $PATCH ==="
echo "baseline: $(cat "$WT.baseline.txt")"
echo "patched : $(cat "$WT.patched.txt")"
else
echo "=== $PATCH === APPLY FAILED"
fi
git worktree remove --force "$WT" >/dev/null 2>&1
如果需要每个测试的细粒度(你需要这个——汇总行会隐藏哪些测试翻盘了),把汇总捕获换成机器可读的报告:
# pytest example: emit JSON-ish results per test
pytest -q --tb=no -rA | grep -E '^(PASSED|FAILED)' | sort > "$WT.after.txt"
# Then diff against the baseline file:
# tests in baseline-pass but not in after-pass = regressions
comm -23 "$WT.before.txt" "$WT.after.txt" > "$WT.regressions.txt"
对同一个 bug 的 N 个候选 patch 跑一遍,你就得到了真正重要的指标:
regression_rate = patches_with_new_failures / patches_that_apply_and_fix
注意分母。不适用的 patch 和没有修好 bug 的 patch 先被过滤掉了——你只关心那些看起来成功的 patch 里隐藏的危险。
用真实 bug 跑了几轮之后,结果往往会聚集成单次试验永远不会显示的模式:
应用失败率大致反映了模型追踪实际仓库状态的能力。幻觉周围上下文的模型产生的 patch 完全无法应用。这很烦人但是安全的——git 会拒绝它们。
修了但回归是危险的分类。模型通过修改一个共享 helper 修复了报告的 bug,而一个无关模块里的两个测试变红了。这些是懒人审查会放过的 patch。
候选之间的一致性比任何单个结果都重要。一个模型出了一个干净的 patch 和四个回归的 patch,说明它的干净输出部分是运气。
这就是我把测试框架输出转化为策略的方式:
阈值是我自己的,不是铁律。关键是跑了半天测试框架之后,你就不再争论一个模型是不是"好的",而是开始争论你自己跑出来的数字。
你的测试套件是天花板。如果你的覆盖率是 40%,0% 的回归率意味着模型在黑暗中碰巧运气好。测试框架测量的是检测到的回归,不是所有回归。
flaky 测试会污染一切。在运行之前先隔离已知的 flaky 测试,否则它们会在每一列都显示为假回归。
候选生成不受控制。问模型要五个 patch 不是五个独立样本;它们共享模型的偏见。这测量的是实践可靠性,不是统计真实性。
它不测试判断力。模型可以通过这个测试框架但仍然做出没有任何测试套件能捕获的糟糕架构选择。
如果你的仓库没有有意义的测试套件,先建测试——没有测试套件这个测试框架没什么可测的。如果你的改动主要是 UI 打磨、复制或配置,回归率是个低信号指标,快速人工审查更便宜。如果你在评估一个模型是为了一个一次性的、用完即弃的脚本,这些都不重要;这个测试框架是用来决定什么可以接近你将维护的代码的。
现在的免费模型领域让人很容易通过问"它修好我的 bug 了吗?"来评估——演示问题。上面这个测试框架花一个下午搭建,就把问题永久地改成了"它修好这个 bug 的频率有多高?"第二个数字才是决定一个模型是节省了你的时间还是把你的调试工作移到了更难看到的地方的指标。
如果你想试试这个,任何模型加任何备用服务器都行——我一直在通过 MonkeyCode 的免费模型访问和免费服务器层跑我的,因为它就在我面前,但脚本不挑。用它跑你自己仓库的真实 bug 历史记录,我真的很想知道你得到什么回归率;我的样本量很小,每个代码库讲的故事都不一样。