18|工程质量的核心:契约、测试、反例、Review与交付证据
目标:让代码质量成为可检验的结果,而不是要求模型“像资深工程师一样”。实验:第27章真实文件修改、8项功能测试、过期验证拒绝。
本章目录
沿着对象与状态理解本章
契约写成可观察行为,反例证明错误,测试绑定版本,审查补足测试没覆盖的风险。
下方图解的固定输入在“输入与适用条件”中声明。它用于解释本章机制,不与本章其他独立例子的数值暗中接续。
测试是找反例,不是给代码盖绿章
为什么“测试都通过”仍可能交付错东西?
测试只能验证它实际检查的行为和版本。一条只断言“不报错”的测试无法证明排除规则正确。好的测试围绕需求构造能击中错误的输入;代码修改以后,旧绿色结果也不再自动属于新版本。
输入与适用条件:构造的文件筛选任务;通配符语义须由需求明确,不在动画里偷偷改变“匹配文件名”与“匹配完整路径”的规则。
1 / 6 · 明确需求
窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。
不先说匹配对象与目录语义,无法判断哪个输出才正确。
本步状态由图形与说明共同给出。默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。
为什么需要
减少返工和虚假完成,而非让审查循环无限增长。
怎样实现
围绕可观察行为、反例和当前版本组织验证。
可能的代价与误解
测试与实现共用同一错误假设、只测顺利路径、频繁审查无实际修复。
如何判断是否做对
故意引入一处已知错误,测试应失败;恢复后再通过。
1. 先定义行为,再决定流程
一个任务是否成功,首先由用户需要什么、现有系统约束什么、哪些行为不能改变决定。对于exclude功能,必须明确路径相对哪里、目录规则如何匹配、是否支持通配、大小写与分隔符语义。若契约不清晰,AI生成的测试只是在确认自己的猜测。
不应为简单修改写几页形式化规范,但应该消除真正影响结果的歧义。两三条可执行例子常常足够;复杂兼容迁移则需要更细的输入、状态和错误语义。设计深度由实际风险决定。
工程环境也应让Agent容易找到构建、测试与架构边界。OpenAI的Harness工程文章强调把知识和可验证约束放进仓库工作方式;SWE-agent研究则说明操作接口会影响编码结果。这些都不意味着必须购买大型编排平台。[N86][N79]
2. 测试金字塔与目标错误
单元测试快速定位局部逻辑;集成测试检查模块边界、真实存储与协议;端到端测试验证用户流程,但通常更昂贵且更易受环境影响。测试组合应覆盖风险,不是追求某个固定数量比例。
例子:仅测试a.py被排除,无法抓住src/a.py的basename误处理。需要同名不同路径、目录边界、Windows分隔符、空规则和父级跳转等反例。每个断言都应对应一条契约或故障假设,避免大量重复的快乐路径。
测试命令、退出码、测试数量、跳过项和环境都应记录。退出码为零却没有发现任何测试,不是功能通过;因缺少依赖跳过关键测试,也不能被总结为“全部通过”。
3. 性质测试与变形测试
有些行为适合用不变性验证:分隔符归一化后结果相同;无匹配规则时增加不相关规则不改变结果;KV缓存分块方式不改变logits;授权用户改变不能复用他人缓存结果。
变形测试在没有完整答案时,通过输入变换推导输出关系。它能补充样例测试,但关系本身必须正确。比如“增加任何排除规则都不应改变结果”就是错误性质,因为增加匹配规则应改变结果。不要把随意猜出的规律当作真理。
数值实现适合用有限差分、守恒量、对称性和高精度基线;Agent流程适合用故障注入、权限边界和版本冲突。把所有东西都交给LLM打分,会失去确定性机制可以提供的强证据。
4. Mutation:证明测试真的能抓错
把因果掩码删掉、把RoPE位置重置、把缓存键中的用户身份拿掉、让finish忽略版本,再运行测试。预期对应测试失败,说明它们对目标错误敏感。如果故意引入Bug仍然全绿,测试覆盖了代码行却没覆盖语义。
Mutation分数也不能单独代表质量。很多机械突变没有业务意义,有些等价变换不应导致失败。优先针对真实失败模式设计负对照,比盲目追求杀死所有变异更有价值。
快照测试适合稳定输出契约,但对自然语言说明、技能全文或无关格式使用固定hash,会让每次合理编辑都变成回归噪声。测试应绑定需要保持的行为,不应绑定整个文本恰好长这样。
5. Review怎样避免成为无限循环
Google的评审标准强调改善整体代码健康度,不追求无限完美。实际审查可围绕设计、功能、复杂度、测试和可读性;小问题可以直接修复并重新验证,不必每次重启方案设计。[N50]
优先检查高后果问题:错误数据写入、竞态、事务边界、兼容破坏、权限扩大、测试被弱化。风格问题在项目已有工具可处理时交给工具。不要让命名偏好掩盖功能缺陷,也不要用“不是大问题”忽略真实风险。
独立Review适合复杂或高风险变化;简单配置修改加聚焦测试可能就足够。评审成本应与减少的缺陷、返工和事故相比较。如果某个流程只延长任务而没有可观察收益,应精简。
6. 验证新鲜度与工件归属
测试通过必须对应当前代码。测试后又修改文件,旧结果不能自动继续有效。本书通过文件版本哈希检测这一点;更大型系统可按依赖范围复用测试结果,但需要可靠依赖与构建缓存,不应凭模型判断“只是小改动”就跳过必要验证。
还需保护用户未提交修改,检查本次diff范围,识别生成文件、锁文件和配置变化。模型可能修复局部问题同时删掉有意保留的代码,最终报告必须按真实diff核对,而不是凭聊天记忆列出文件。
部署也是独立动作。代码通过本地测试不等于已经上线;PR合并不等于生产健康;回滚方案是否可用取决于数据迁移与外部副作用。报告应明确每个阶段完成状态,不虚构生产验证。
7. 一份简洁但充分的交付
内容包括:改了什么行为、为什么需要、主要影响范围、运行了什么验证、剩余限制。复杂任务可附关键决策理由;简单任务不必塞满模板。如果证据不足,应具体指出缺什么,而不是用“建议进一步测试”泛泛结束。
验收题:模型补丁通过全部现有测试,却不满足用户新要求,责任在哪?测试集没覆盖新契约,不能以旧测试替代需求验收。应增加针对新行为的测试,并检查旧行为是否仍保留。AI工程质量首先还是软件工程质量。
性质测试用一个族的输入抓住前缀错误
输入与条件。排除目录d。正确关系是d本身和d/child排除,d_suffix保留。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1实例1 | d="a" | a、a/x排除,ab保留。 |
| 2实例2 | d="cache" | cache/tmp排除,cached保留。 |
| 3生成族 | 改变d与后缀 | 重复同一边界性质。 |
| 4变异实现 | 把边界检查替成startswith(d) | 多个反例稳定击中错误。 |
| 5约束范围 | 先规范化路径与分隔符 | 否则字符串生成可能不对应真实路径语义。 |
把过程连起来。性质测试把需求变成对一类输入的关系,变异或反例用来检验测试是否真能抓错。
边界与反例。通过有限生成样本不证明所有路径正确;平台与安全边界需要单独验证。
依据与适用范围:[DERIVE]。示例输入和场景由本书明确设定。