晚上 11 点半,AI 丢下一句「测试全过 ✅」,你关了电脑睡觉;早上打开页面,新加的按钮根本没反应。
这种「假完成」我上周遇到三次——AI 一口气报 5 次「全过」,3 次是假的,全是旧数据被覆盖这类无声坑。它是真觉得自己完成了,因为它的测试压根抓不住。问题不出在测试数量,出在断言设计。
这是 AI 编程避坑系列第 2 篇,承接《5 步驯服 Coding Agent》:那篇讲流程,这篇讲第三步「验收检查怎么写」里最容易翻车的一环。
先掰词
断言 = 测试里写死的一句「什么才算对」。 像验货对着清单逐项打勾,不是快递说「到了」就算到。断言只写「成功了没」,没写「它把旧数据覆盖了没」,测试全绿也拦不住假完成。
断言设计 4 步
第 1 步:预期和「不应发生」都要写
一条完整用例 = 前置条件 + 输入操作 + 预期结果 + 不该出现什么 + 怎么检查。
拿「导入 2 条新消息」举例:断言总数加 2 之外,还必须断言旧记录的原文、发布者一个字没变。关键字段逐个比,别只数条数。我练习题里的真实写法:
def import_messages(existing: list[dict], batch: list[dict]) -> list[dict]:
seen = {(m["room_id"], m["msg_id"]) for m in existing}
result = list(existing) # 不改动已有记录(原文/发送者/采集时间保留)
for m in batch:
key = (m["room_id"], m["msg_id"])
if key not in seen: # 身份 = 房间 + 消息ID,不是文本
seen.add(key)
result.append(dict(m)) # 拷贝存入,避免外部对象后续被改
return result
配套断言不能只写 len(result) == 2,必须逐字段比对已存在的 M01 原文未被改写——只检查「成功」,就永远发现不了「它把错误内容覆盖进去了」。
第 2 步:红灯要坏得对
TDD 先红再绿,但红的失败原因必须是「目标行为还没实现」。缺依赖、模块导不进来,那是考场坏了,不算验收——先修运行条件,这条测试还没开始检查业务规则。
测试第一次就直接绿:别故意改坏代码造假红灯。要么这个行为早已实现(保留测试即可),要么你的断言漏了关键结果。后者更常见。
第 3 步:查逻辑——固定假数据 + 模型替身
去重、筛选、引用越界这类题有标准答案,像数学题。喂固定假数据,把模型换成替身(它自己记录被调用几次),逐字段断言。数数是程序干的活,不归模型:
- 快照为空 → 断言
call_count == 0,模型一次都不许被调 - 引用越界 → 断言被拒,返回越界的 ID 清单
def validate_answer(answer: dict, input_ids: set[str]) -> tuple[bool, list[str]]:
bad = [i for i in answer["cited_ids"] if i not in input_ids]
return (len(bad) == 0, bad)
替身的价值是稳定制造「没调用 / 乱调用」这些边界,真模型反而演不出来。
第 4 步:看效果——真模型多跑几轮
摘要准不准、有没有编——程序能校验「引用没越界」,校验不了「引用真的支撑了回答」。这类必须真实调用 + 多样本 + 语义标准:允许措辞变,固定住「意思必须保留」。比如「爬长城很累吗」和「爬到一半能返回吗」被概括时,两条的差异有没有被揉掉——这是语义检查,替身做不了。
一次通过不等于稳定,同一条至少跑几轮看一致性。
速查表
- 用例五件事:前置|输入|预期|不应发生|检查方法
- 关键字段逐个比,不只数条数
- 红灯 = 目标行为没实现,不是考场坏了;第一次就绿先查断言漏没漏
- 逻辑用替身逐字段断言,效果用真模型多样本多跑几轮
验收时多看一眼
AI 报告「测试全部通过」时,去它的汇报里找两行:真实模型调用过没有?页面实际操作过没有?我那 3 次假完成,两次都是单元测试全绿、但「测试全过」覆盖的只是程序逻辑那一半——模型效果和用户流程根本没被执行过。
一句话收束:AI 写代码算不算做完,不能它嘴上说了算,断言说了算。
本篇最早于 2026 年 10 月 19 日发布于小红书,此为博客完整版。