AI 生成代码的属性测试:用不变量覆盖模型没想到的输入
示例测试容易和模型实现共享盲点。本文从需求提取不变量,设计生成器、收缩器与确定性种子,并用反例回归门验证 AI 生成代码的输入空间。
本文要点
- 主题聚焦:AI Agent、TypeScript、开源
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
让同一个模型生成函数和三个示例测试,常得到一套自洽但错误的答案:实现把空字符串当成缺失值,测试也从未生成空字符串;分页代码漏掉最后一页,测试只覆盖整除样本。属性测试不枚举预期输出,而是反复生成满足领域约束的输入,检查任何合法输入都必须成立的不变量,因此特别适合发现实现与示例共享的盲点。
它也不是“随机跑很多次”。可用的门禁必须说明生成空间、拒绝比例、运行次数、随机种子、收缩后的最小反例和重放命令。如何让模型协助产生初始样例可参考AI 测试生成,但不变量必须来自规格、调用方和领域规则,不能由待测实现反向推导。
先写失败判据,再写生成器
评审任务时,把自然语言需求拆成四类属性:
- 保持性:排序不改变元素多重集合,序列化再解析保留数据。
- 代数性:规范化满足幂等,集合合并满足交换或结合条件。
- 边界性:页码、长度、金额和时间永不越过约定范围。
- 关系性:优化实现与简单参考实现对同一输入给出相同结果。
“不会崩溃”通常太弱,“结果等于当前实现输出”则没有检错能力。以 slug 规范化为例,可要求 normalize(normalize(x)) === normalize(x)、结果只含允许字符、最大长度不超过 80,且两个不同的受保护名称不得碰撞。若需求不清楚哪些 Unicode 字符应等价,属性测试会把规格空洞暴露出来,此时应先澄清而非让 Agent 猜测。
生成器要编码合法域,而不是先生成任意值再大量 filter。订单生成器应直接产生非负整数分、受支持币种和合法税率;需要测试非法输入时另建生成器并声明预期错误。复合类型可以从小结构开始,限制数组长度与递归深度,避免 99% 时间消耗在不具代表性的巨型对象上。TypeScript 高级类型能约束编译期形状,但运行时边界仍需生成器验证。
固定环境并保留每一个反例
团队将 fast-check 以精确版本写入依赖并提交 lockfile。以 Node 22.18.0、pnpm 10.14.0 为基线,CI 从干净检出执行测试,并把测试框架与属性库版本写入报告:
mkdir -p .artifacts
pnpm install --frozen-lockfile
node --version
pnpm --version
pnpm exec vitest --version
pnpm exec vitest run tests/property --reporter=json \
--outputFile=.artifacts/property-tests.json
属性配置明确 seed、numRuns、endOnFailure 和超时。PR 快速门可用冻结种子执行 500 次,夜间门再执行 10 个派生种子各 5000 次。失败日志必须包含属性名称、seed、path、原始输入和收缩结果。以 fast-check 为例,重放时用同一 seed 与 path,而不是复制一个可能丢失编码细节的控制台字符串。
发现反例后,将最小反例加入普通回归测试,并保留触发它的属性。只保存例子会再次缩小探索空间;只依赖随机重现则可能因库升级改变生成序列。反例包含敏感数据时,保存可公开的最小结构和哈希,原始输入进入受控工件库。
设置能阻断合并的质量门
属性测试报告至少检查:所有批准属性通过;有效样本数达到预算;过滤拒绝率低于 20%;没有超时或未捕获异步任务;历史反例全部重放成功。金额、权限和解析器等高风险代码还需通过参考实现差分测试。门禁绑定 headSha + lockfileHash + propertyConfigHash,测试后任何源码或生成器变更都会使证据失效。
若失败,Agent 先把反例分类为实现错误、规格歧义、生成器越界或测试预言错误。实现错误可修补后重跑完整种子集;规格歧义转人工;生成器错误只能在证明输入不属于合法域时修改;不得因为某个值难处理就增加过滤器。连续两次自动修复仍失败,停止改动并提交最小反例、调用栈和候选根因。
属性测试还要防止假绿。异步属性必须返回或等待 Promise,外部服务应换成可确定的模型或记录重放,时间与随机源显式注入。并发逻辑不能用“睡眠 100 毫秒”证明正确,应生成事件顺序并检查状态机不变量。TypeScript 函数式编程中的纯函数与不可变数据能显著降低这种测试成本。
用缺陷样本验证属性本身
在启用门禁前,向实现注入一组已知错误:去掉边界比较、调换输入顺序、破坏一次往返、截断 Unicode、重复应用副作用。属性若捕捉不到相应错误,说明不变量或生成域仍不足。再将属性层和Agent 测试策略中的单元、集成与端到端层组合:属性负责广度,示例负责业务叙事,集成测试负责真实边界。
高质量属性测试最后交付的不是“随机测试通过”,而是一份可审计证据:哪些输入被探索、哪些不变量被证明到什么预算、失败如何精确重放、库版本变化时为何需要重建基线。这使 AI 生成代码必须面对模型没有预先编排的输入,而不是只通过自己写给自己的考题。
Related