用测试契约约束AI生成代码
ParkEase采用先失败测试、后实现的流程,并为22项任务维护测试登记表,累计形成3246个单元测试和1080个集成测试。集成测试连接临时容器中的真实PostgreSQL与PostGIS,另有浏览器端到端测试。
AI 深度解读
AI 写完代码,再让 AI 补测试,很容易得到一套证明「代码就是这么运行的」的测试,连错误也一起固化。ParkEase 作者的做法,是先定义验收条件,再让生成的代码满足它。
这个面向印度的个人车位交易平台,由作者独自使用 Claude Code 开发,最终积累了 3,246 个单元测试、1,080 个集成测试,以及浏览器端到端测试。比数量更值得看的是:测试如何规划、哪些必须通过,以及为什么全部通过后,首次部署仍然发现了问题。
先写测试,是为了让 AI 有明确的验收条件
作者把测试驱动开发(TDD)放进每次构建流程:先写失败的测试,确认它确实失败,再写实现,最后确认通过。
顺序为什么重要?如果实现已经存在,AI 很容易照着现有行为写断言。代码算错了,测试也可能把错误结果当成正确答案。先写测试,至少能让预期行为先于实现被表达出来。
测试应该约束生成的代码,而不是替生成的代码作证。
但只有执行顺序还不够。ParkEase 拆成了 22 个任务,每个任务开始编码前,都要在测试登记表里写清楚:测什么、在哪一层测、为什么需要测。这个文件最终达到 2,357 行,并随功能交付同步更新。
它解决的是覆盖范围的可追溯性。有人问某个功能有没有测试,可以查具体记录,而不必凭印象回答。不过,登记表记录完整,不等于断言一定正确,后面的部署事故就说明了这一点。
数据库里的规则,必须交给真实数据库验证
ParkEase 的集成测试使用临时容器,运行真实的 PostgreSQL 18 和 PostGIS,不模拟数据库。
理由很具体:防止预约时间重叠的规则,由数据库的排他约束执行;账本也必须满足借贷平衡。如果模拟数据库没有实现这些约束,业务代码即使通过测试,也无法证明真实环境会正确拒绝冲突。
作者把四组集成测试设为每次变更必须通过的检查:
- 预约并发测试保证最后一个车位只能售出一次,其中一个测试会删除数据库约束,确认重复售出重新出现,用来验证约束确实起了作用。
- 账本测试检查每笔交易的借方总额与贷方总额相等。
- 幂等性测试检查请求重试不会造成重复扣款。
- 搜索性能测试要求在 10,000 个车位上,p95 低于 200 ms,并在查询计划不再使用空间索引时判定失败。
这里的 p95,可以理解为第 95 百分位的响应耗时,它比平均值更容易暴露较慢请求的问题。
另外还有压力测试:100 名司机争抢 10 个车位,结果必须恰好是 10 笔预约、90 次正常冲突,没有服务器错误,且 p95 低于 2 秒。它同时检查业务结果和响应表现,避免把「没有报错」当成并发正确。
测试通过,为什么上线仍然会出错
作者有两条规则,是漏过缺陷后补上的。
第一,API 测试必须真正经过 HTTP。直接调用内部命令,会绕过守卫、拦截器、状态码和错误响应结构。内部函数返回正确,不代表客户端收到的结果正确。
第二,新页面必须有新测试。移动端最终有 1,466 个组件测试,还用表驱动测试逐一检查数据页面是否具备加载、错误和空状态。Web 与管理端使用 Playwright,在手机和桌面尺寸下跑端到端测试,每个页面还执行无障碍扫描。
提交 PR 前,本地必须通过 lint、typecheck、全部单元测试、四组强制集成测试、依赖审计和密钥扫描,并把输出贴进 PR。作者要求先在本地完成检查,不能拿「CI 会发现」作为提前提交的理由。
但首次部署还是发现了 3 个缺陷。原因是测试夹具(fixtures,即预设测试数据)与实现共享了同一种错误的 API 假设,双方对得上,却都与真实接口不符。修复方式是从共享契约导入真实响应结构,替代手写夹具。
还有一个明确缺口:移动端端到端流程虽然写好了,但需要真实 Android 设备才能运行。作者记录了这个缺口,没有把它算成已经验证。
我的判断
我认同这套方案最有价值的部分,是把关键业务规则落实成可失败的检查。尤其是删除数据库约束、观察重复售出重新出现,比单纯检查一次成功结果更有说服力。
但测试数量不是质量证明。原文没有给出采用这套流程前后的缺陷率、维护成本或对照实验,因此「先写测试更适合 AI」仍主要是作者的实践判断。
真实基础设施也应按边界使用。数据库约束、空间索引、事务行为适合用真实 PostgreSQL 验证;原文没有证明所有测试都需要替换掉 mock。对一般团队来说,单元测试保持快速,集成测试覆盖真实依赖,通常更容易控制反馈时间。
性能数字同样有前提。原文没交代硬件、数据分布和完整压测条件,200 ms 与 2 秒可以作为该项目的验收目标,不能直接当作其他系统的性能基准。共享契约能减少手写结构漂移,但契约本身是否准确,仍需要核验。
能拿来做什么
- 给下一项 AI 编程任务先写验收清单,列出正常路径、冲突路径和失败条件,再让 AI 写实现;同时确认测试在实现前确实会失败。
- 如果 Node 服务依赖数据库约束或索引,挑出最关键的规则,在临时真实数据库上验证,并检查规则失效时测试能否发现。
- 为前端数据页面统一检查加载、错误和空状态,让新页面接入时必须补齐对应测试。
- 核对现有 API 测试是否经过 HTTP,并区分「测试已编写」与「测试已执行」;依赖真实设备的流程,应明确记录尚未验证的范围。