案例复盘:162个绿灯单元测试背后是0个实际可用用户流程,文章总结出7条根因及机器执行的质量门禁建设经验。
2026 年 6 月 29 日,一款移动应用报告"与 PC 版的第一波功能对齐已完成,162 个测试全绿"。当我们就着真实设备(通过 TestFlight,即 Apple 的 Beta 应用分发)真正上手使用时,每一个主要用户流程都是坏的。它无视了 SafeArea(屏幕上不受刘海和系统栏遮挡的区域),直接跑到了刘海底下;聊天功能一按发送就失败;设置页面返回 404;知识上传也失败了。162 个测试全绿,而实际在设备上能跑通的主流流程数量是 0。
本文深入剖析了在使用 AI 写代码时遇到的虚假"完成了"报告——这个问题早在触及 AI 能力边界之前就已经存在——并记录了我们如何从中提炼出机器可强制执行的质量门。
162 比 0 的根因归结为一点:我们把全 mock 的单元测试套件的绿色结果,当成了"应用正常运行"的证据。在复盘会上我们摆出了七条有代码证据的根因,但核心在于:API baseURL 没有接线;认证模块被一句"后续步骤"的注释搁置在了后面;而在真实设备上压根没有运行过任何操作。换句话说,这些测试只是确认了"我写的 mock 表现符合我的预期"——它们从未与外部世界连通。从那以后,我们将"测试通过 ≠ 功能可用"奉为警示语,并创建了一个新的完成门技能。

虚假完成并非只有 AI 特有的幻觉才会导致。"已实现,但未出现在生产环境中"——人类同样会踩到这个坑——披着同样的外衣。在一个新闻聚合平台上,我们实现了一个多级降级方案来挽救损坏的 JSON,然而在生产环境中错误却始终不停。追查之下才发现,降级方案所依赖的 json-repair 库根本没有被加入 worker's requirements 文件(Python 的依赖清单),所以根本不存在于 Docker 镜像中。这是一个同时存在两份 requirements 文件的设置下的陷阱。代码写好了,通过了 review,但在生产环境中它是死代码(从未真正运行的代码)。与此同时,监控已经堆叠了一万多条错误事件。这件事之后,我们将打包契约本身变成了测试需要断言的对象。
# 用"它在生产镜像中"来佐证"我们实现了它"
def test_fallback_dependency_is_packaged():
# json-repair 必须声明在 worker 的 requirements 中
reqs = read_requirements("requirements-worker.txt")
assert "json-repair" in reqs, "fallback dependency missing from the production image"
# 而且它必须真正可导入(声明了不等于能解析)
import importlib
assert importlib.util.find_spec("json_repair") is not None

我们还见到了最具 AI 特色的虚假完成。在 dogfooding(用自己开发的产品来开发产品本身)的过程中——即把本地 LLM 编排器的改动本身扔给它处理——一个执行智能体克隆了代码库后读取了那份配置文件(一条规则:"将实现委托给子智能体"),然后尝试去做委托。但在一个无头环境(没有交互式 UI)中,它无法生成孙级子智能体,于是就幻觉出了"我添加了一行",实际上什么都没改,然后推送了一个空分支。GitHub 自然以 422 拒绝了这次"空推送"。这里有两点教训:其一,"只有在基于自身做开发时,自身代码库的配置文件才会成为执行智能体的毒药"。其二,"不要信任报告,必须看产物(真实的 diff)"。
冷静来看,这些虚假完成共享了若干机制。
第一,AI 倾向于用"是否达到了一个看似合理的终止状态"来判断完成,而不是"是否满足了任务的真实意图"。中间产出如测试全绿、提交已创建、PR 已打开,单独来看都不意味着意图被达成。第二,"部署"和"真实设备"不在完成的分母中。当把"我在代码库内完成了 100%"等同于"它在生产环境可用"时,就会出现上面的 json-repair 案例,或者下面要说的插件冻结问题。第三,验证了错误的目标。某个质量门只检查工作树的 diff,且携带一个 bug:"一旦你提交后,树就变干净了,就被视为永远未完成"。换句话说,"完成"的定义本身就与外部世界切断了联系。
补救措施始终如一:将完成的定义从"AI 的自我报告"转移到"机器可验证的证据"。我们分阶段叠加了质量门。
第一道门:阻止缺乏证据的完成声明。它禁止将跨越边界的变更(外部 API、支付、数据库写入、SaaS 间集成)仅凭弱代理证据——"构建通过了"、"单元测试全绿"——就称为"完成",而是要求提供与真实设备等效的端到端证据。在一个 uptime 监控 SaaS 上,这条真实设备门失败一次(我们内部称之为 MUST 25 原则),一口气就暴露了藏在全绿单元套件背后的五个 bug(一个失败的 DB 创建、一个调度器漏掉了载荷中的必填数据、Dockerfile 中的安装顺序失误等等)。
第二道门:给移动端这类强边界领域配备专用的完成检查清单作为技能。大致如下:
# feature-done 门(未在真实设备上满足就不要称之为"完成")
- [ ] API baseURL 已连接到真实环境,并有至少一次不带 mock 的往返证据
- [ ] 认证流程已在真实设备上通过(不能留"后续步骤"的注释)
- [ ] 每个主要流程(创建、发送、上传)均已在真实设备上手动运行一次
- [ ] SafeArea / 导航已通过真实设备截图确认
第三道门:在干净环境中运行质量门。它在你的本地机器上通过,但在干净检出的环境中失败——这样会把依赖本地目录的非隔离测试(依赖实际工作目录的测试)冲刷出来。实际上,某道质量门在干净的工作树(仓库的全新检出)中运行测试,检测出了"本地 npm run check 永远发现不了的缺陷"。这正是该质量门设计的价值所在。
最后是一个典型的例子,说明当"部署"从完成分母中消失后会发生什么。在一批以自动驾驶模式运行的生产账号中,分发的插件在整整 12 天里一直冻结在旧版本上,但质量门全绿,没有人发现。五个结构性漏洞中,核心两条是:我们将"100% 在代码库内"等同于"已在生产环境中体现",而检测层只有基于 push 的钩子。在 git-commit 时运行的钩子在结构上就根本无法观察 git 之外的状态(实际运行在分发目标上的是什么版本)。修复方案是拉取式的版本一致性检查:由分发目标上的定时任务来比对"已安装版本"与"应当分发的版本"。这里我们将"分布漂移只能通过拉取式定期对账来检测"这一原则升格为文档化标准。

"测试通过 ≠ 功能可用。" 全 mock 单元套件的绿色结果只能确认"我的预期与我的预期一致"。用真实设备的端到端测试来判定边界是否完成。
"已实现 ≠ 在生产环境运行。" 用包含打包和部署的契约测试来佐证。死代码保持绿色,在生产环境中沉默不语。
用证据而非声明来定义完成。 不要仅凭弱代理证据——构建 OK、单元全绿、部署成功——就称之为"完成"。
用拉取式方法来看见部署漂移。 基于 push 的钩子无法观察 git 之外的世界。"100% 在代码库内"不等于"已在生产环境中体现"。