AI研发流程初步实践(二):编码准确性与测试基线
AI写的代码有一个极具欺骗性的特点:它看起来对。命名合理、结构整齐、注释齐全,甚至比大多数人类开发者的代码还要”干净”。但运行起来才发现——引用了不存在的API、漏掉了边界条件、在未测试的路径里埋了bug。这不是AI故意使坏,而是大语言模型的本质决定的:它生成的代码基于概率分布,不是基于执行验证。要让AI写的代码从”能跑”提升到”可信”,需要一套相互嵌套的质量防线。
一、概览:AI代码的质量命题
AI辅助开发带来了一个前所未有的质量命题。传统软件开发中,代码的质量由开发者直接负责——你写一行代码,你知道它为什么这么写,你知道它覆盖了什么、没覆盖什么。AI不是这样工作的。
AI生成代码的过程可以类比为”一个经验丰富的程序员在键盘上睡着了,手还在动”。它输出的代码在语法和风格上无可挑剔,但可能在逻辑上引用了一个不存在的函数、假设了一个从未被赋值过的变量、忽略了一个关键的状态转换。问题的根源在于:AI没有执行模型。它不知道自己的代码运行起来会怎样,它只是”猜”运行起来会怎样。
这让传统代码review在很大程度上失效。人类review AI代码时,很容易被表面的整洁误导,认为”代码这么规范,逻辑应该也没问题”。更糟糕的是,AI代码的bug往往不是语法级别的(lint工具抓不住),而是逻辑语义级别的——函数签名看起来合理但行为有微妙偏差、错误处理路径看起来覆盖了但实际走不到。
解决这个问题的思路是:不再依赖人的判断,而是依赖机制的连锁。当TDD、版本控制、UAT、测试基线这四层防线叠在一起时,每一层覆盖上一层可能遗漏的漏洞:
- TDD 守住每个函数的正确性——测试不通过,代码不进仓库
- 版本控制 守住每次变更的可回退性——出问题能立即回退
- UAT 守住端到端的行为一致性——spec写了什么,就验证什么
- 测试基线 守住不退化——新版本不能比老版本差
这种”层层兜底”的设计,是让AI代码从”看起来对”变成”真的对”的唯一可靠路径。
二、通用设计方案:四层质量防线
2.1第一层:TDD红线
在AI辅助开发里,TDD的地位必须从”最佳实践建议”提升到红线。红线意味着:严禁跳过测试直接写业务代码,这条规则没有例外,哪怕是”这个函数很简单”或”这个改动只是改个常量”。
为什么必须这么硬?因为AI对”简单”的判断不可信。它认为”把timeout从30s改成60s”很简单,但忽略了同一个常量同时被流式控制和工具超时共用,改了一处导致另一处行为变化;它认为”加个日志打印”很简单,但日志里泄露了敏感字段。TDD的价值不是”测这个函数对不对”,而是强制把行为变化显式化——你要改行为,就必须先写一个测试描述新行为、看到旧测试RED、再改代码。
TDD的红绿循环在AI辅助开发中有三个严格执行的步骤:
- RED:先写测试,运行,必须在终端看到失败。看不到RED就写实现,等于没写测试——因为你无法判断测试是否真的在测你关心的东西。
- GREEN:写最小代码让测试通过。不许多写一行”顺便”的代码。AI倾向于”一步到位”写出完整实现,这违反YAGNI,也让后续任务无活可干。
- REFACTOR:测试通过后才能重构。重构后再跑一次测试确认仍绿。
为什么”先见证RED”如此重要?因为AI经常写出”看起来对但永远通过”的测试——断言写错变量名、测试根本没调用被测函数、mock配置让任何输入都通过。先跑一次看到RED,证明测试真的在测你想测的东西,GREEN阶段的实现才是有意义的。
2.2第二层:版本号约定
AI辅助开发的项目,版本号不是装饰。每次发布新版本,version必须语义化,CHANGELOG必须同步更新。
Semantic Versioning(语义化版本) 的MAJOR.MINOR.PATCH规则:
- MAJOR:不兼容的API变更。在AI辅助项目里通常意味着架构层重构。
- MINOR:向后兼容的功能新增。一个迭代周期完成一组新能力,升MINOR。
- PATCH:向后兼容的bug修复。
Keep a Changelog 的格式约定:每个版本条目下分Added / Changed / Deprecated / Removed / Fixed / Security六类。AI写CHANGELOG容易犯两个典型错误:一是把所有改动堆在一起无分类,二是写得过于技术细节(”重构了JSON解析器的buffer管理”)而忽略用户视角(”修复了大文件解析时的内存溢出”)。CHANGELOG是给用户看的,不是给git log看的。
version、CHANGELOG、git tag三者必须同步:package.json 的version字段、CHANGELOG.md 的条目、git tag v0.x.y 的标签,缺一不可。封仓时强制检查这三项一致,不一致不允许收尾。
2.3第三层:UAT核验清单
UAT(User Acceptance Testing,用户验收测试)不是”跑一遍看看”,而是一份结构化清单,分四类核验:
第一类:local核验
本地开发环境必须跑通。全量测试绿、TypeScript编译零错、新功能手动走一遍核心路径。这是最低门槛——连本地都过不了的代码不应该进入下一步。
第二类:VPS核验
部署到生产环境(或预发环境)跑通。本地通过不代表VPS通过——文件路径差异、Node版本差异、环境变量缺失、data目录权限,任何一项都可能让VPS挂掉。AI写的代码在本地测试环境里经常”假装”所有路径都正确,因为它不会预见到部署环境的差异。
第三类:新功能逐项核验
按spec的验收标准一条一条过。spec里写了”访问 /learn看到19篇文章卡片”,UAT时就真的访问 /learn数卡片数量。spec是契约,UAT是验收,一一对应。这里没有”看起来差不多”的余地。
第四类:老功能回归核验
上一版本的核心能力全部跑一遍。AI改动代码时经常”无意中”破坏老功能——新增依赖把某个老API的行为改了、重构时漏改了一个调用点、升级依赖后某个接口不再兼容。回归核验是最后的兜底。
四类核验缺一不可。只做local不做VPS,会在部署日翻车;只做新功能不回归,会在用户反馈里翻车。UAT清单写成markdown文件,逐项打勾,全部通过才可以发版。
2.4第四层:E2E测试设计
E2E(端到端)测试覆盖两类场景,同时避免一种常见反模式:
happy path:用户最常走的路径,从入口到出口完整跑通。比如”用户访问首页 → 点击文章卡片 → 看到正文 → 提交反馈 → 收到成功提示”。这条路径任何一个环节断了,核心体验就崩了。
error path:异常场景。slug不存在返回404、message为空返回400、连续提交触发限流429、无auth访问管理接口返回401。error path测试是AI最容易漏的——它写代码时默认一切正常,不会主动想到”如果用户输入空字符串会怎样”。
zero expect反模式:测试里只有 await page.goto(url) 没有 expect。这种测试永远通过,毫无价值。每个测试必须有明确的断言——页面包含某段文字、状态码等于某值、数据库多了一条记录。
E2E测试的成本很高,所以要有取舍。不追求100% 覆盖,但核心路径 + 关键错误路径必须覆盖。视觉细节(字号、颜色、间距)不写E2E,留给手动UAT或未来的视觉回归工具。
2.5测试基线维护
每个版本有一个测试基线数字(比如v0.2.2是936/938 passing)。下一版本的目标是:总数只增不减、通过率不退化。
不退化红线的含义:
- 不删除老测试来”修绿”:测试红了一定是代码坏了,不是测试坏了。
- 不skip测试来”绕绿”:
it.skip是临时手段,封仓前必须恢复。永久跳过的测试等于没有测试。 - 新功能必须配新测试:代码量增加而测试量不增加,覆盖率必然下降。
- flaky测试必须治理:偶发失败的测试比不测试还糟——它让”红”失去了警示意义。flaky要么修、要么隔离、要么删除,不能放着不管。
测试基线的数字要在spec里预先写明(如”0.2.3目标:新增约85-95项测试,总数达 ~1030-1050”),封仓时核对。未达目标的版本不发——这是给自己的硬约束。
2.6封仓收尾流程
封仓不是”提交最后一个commit”,而是一套结构化收尾流程。典型流程包含:
- 所有subtask完成(看板全
[x]) - 测试全绿 + 编译零错
- CHANGELOG / README / 架构文档同步
package.jsonversion升位- git tag创建
- 分支合并方向确认
这套流程的价值是防止”差不多就发”。AI在最后阶段容易松懈——“测试都过了,文档下次再补””tag等想起来再打”。结构化收尾把这些”下次”逼成”现在”,因为每一项都是封仓的硬条件。
文档同步尤其重要。CHANGELOG不更新,用户不知道这版改了什么;README不更新,新功能没人知道存在;架构文档不更新,三个月后连你自己都忘了为什么引入这层抽象。
2.7四层质量防线总览
下图展示了这四层防线如何层层嵌套、相互兜底:

TDD保证每个函数的正确性,版本控制保证每次变更的可回退性,UAT保证端到端的行为一致,测试基线保证版本之间的非退化——四层缺一不可。
三、市面其他方案对比
围绕”AI代码的质量保障”,市面上的实践大致可以归纳为三种方案。它们代表了不同的取舍。
3.1方案A:无测试,AI写完就部署
这是最激进也最大胆的方式——让AI写完代码,直接部署上线。不写测试,不做review,不跑UAT。
设计特点:
- 零测试投入:所有开发时间都花在写业务代码上
- 完全信任AI:假设AI的输出足够正确
- 依靠运行时发现bug:错就错了,线上发现再修
- 快速上线:从想法到部署的时间最短
适用场景:原型验证、个人小工具、不重要的内部脚本。当代码出错不会造成实际损失时,这套方式最快。
局限性:对于任何面向用户、处理数据、涉及资金的项目,这是最危险的方式。AI代码的bug不是”可能出错”,而是”一定会出错”,只是你还没发现它在哪。线上修bug的成本通常是开发阶段修复的10-100倍。
3.2方案B:人工事后测试
代码由AI生成后,人类开发者写测试来验证。这是很多团队的实践方式——AI写代码,人写测试。
设计特点:
- 开发和测试分离:AI产出代码,人类补充测试
- 覆盖不确定性:人类决定测什么、不测什么
- 测试滞后于代码:测试在代码写完之后才写
- 依赖人的判断力:人需要判断哪些地方值得测试、AI可能在哪些地方犯错
适用场景:人力充足、对质量有一定要求但不追求极致的项目。
局限性:这种方式有几个根本问题。第一,覆盖率难以保证——人类reviewer会被AI代码的”整洁外表”欺骗,漏掉关键测试。第二,测试与代码不同步——AI改代码时不会更新测试,因为测试是”事后补的”而不是”事先写的”。第三,人写测试的习惯性偏见——人类倾向于测试”代码做了什么”而不是”代码应该做什么”,这是TDD反过来的视角,更容易漏掉边界条件。第四,人的精力有限——AI每小时可以产出数千行代码,人写测试的速度远远跟不上,最终测试覆盖率会越来越低。
3.3方案C:TDD先行 + 自动化测试基线 + UAT核验
这就是本文详细阐述的方案——TDD红线约束编码过程,自动化测试基线维护质量门槛,UAT结构化清单做发布前的最终验证。
设计特点:
- TDD先行:测试先于代码,强制把行为变化显式化
- 自动化基线:测试数量和质量作为版本发布的硬指标
- 结构化UAT:四类核验清单逐项打勾
- 封仓流程:版本发布的标准化收尾
- 多重兜底:每层防线覆盖上一层的盲区
适用场景:长期迭代的产品项目、多人协作的团队项目、面向用户的生产级代码。
代价:开发速度最慢。写测试比写代码花的时间更多,TDD红绿循环的过程比直接写代码慢得多。UAT核验需要手动执行的时间成本。这些投入在长期项目中是值得的,但在短期项目或原型阶段可能不划算。
3.4对比总结
| 维度 | 方案A(无测试) | 方案B(人工事后测试) | 方案C(TDD + 基线 + UAT) |
|---|---|---|---|
| 开发速度 | 最快 | 中 | 最慢 |
| 测试覆盖 | 无 | 中(依赖人) | 高(结构化) |
| 测试与代码同步 | — | 滞后 | 先行(TDD) |
| bug发现时机 | 线上 | 测试阶段 | 编码阶段 |
| 长期维护性 | 差 | 中 | 好 |
| 人力投入 | 最低 | 最高(人写测试) | 中(AI写测试 + 人审核) |
| 适用项目 | 一次性脚本 | 中等复杂度 | 长期产品 |
这个对比揭示了一个反直觉的事实:方案C看似最慢,但在长期项目中反而最省时间。因为方案A和方案B的bug修复成本会随着项目增长而指数级上升——你需要花越来越多的时间debug、回溯、回退。方案C的投入主要在前期,后期则持续享受测试基线带来的安全感。
四、aptbot的设计特点
4.1为什么选方案C
aptbot是一个开源学习型AI Agent项目。它的代码本身就是为了让人学习agent开发而存在的。这两个目标——开源、教学——决定了它必须选择方案C。
首先,作为一个开源项目,aptbot的代码会被其他人使用和修改。如果没有测试基线,贡献者在修改代码时无法判断自己的改动是否破坏了现有功能。TDD红线和自动化测试基线让开源协作成为可能。
其次,作为一个学习型项目,aptbot的测试本身就是教学材料。读者看测试代码就能理解”这个函数应该表现什么行为””边界条件是怎么处理的”。测试不仅仅是质量保障工具,也是文档。
第三,aptbot的目标是长期可持续的。它不是一个”写出来就跑”的项目,而是一个会持续迭代的assistant。方案C的质量投入会在后续版本中持续产生回报。
4.2 aptbot的独特做法
aptbot在方案C的基础上,做了几个独特的设计:
TDD工具化红线:在其他方案C实践中,TDD通常是”团队约定”或”个人习惯”。aptbot把TDD编码为 test-driven-development skill——agent在执行任务时被skill约束,试图跳过测试会被拦截。这让TDD从”口头约定”变成了”系统强制”。
finishing-a-development-branch skill:封仓收尾流程被封装为skill,自动执行版本检查、文档同步、tag创建等操作。不需要人工对照checklist逐项检查,agent自己完成这些验证。
UAT清单与spec的双向绑定:UAT清单不是独立存在的,而是直接从spec的验收标准章节生成。spec里写了什么验收标准,UAT就验什么。这保证了”写了的都会验,没写的不算完成”。
测试基线在spec中预先声明:每个版本的spec会在”测试策略”章节写明预期增加的测试数量和目标基线。这不是事后统计,而是事先承诺。
4.3与其他方案的差异
和方案A、B、C相比,aptbot最本质的差异在于”把质量保障从人与人的契约,变为人与系统的契约“。
在方案A中,质量靠运气;在方案B中,质量靠人的责任心;在方案C的典型实践中,质量靠团队纪律。aptbot更进一步——质量靠系统强制。agent无法跳过TDD、无法绕过UAT、无法在基线未达标的情况下封仓。纪律不是靠”遵守”,而是靠”无法违反”。
这种设计哲学和aptbot作为agent项目的定位高度一致:如果agent自己写代码的质量都要靠人盯着,那它就不是真正的agent。
五、发展方向
测试的自动生成与自动修复:当前TDD中的”写测试”还是AI在执行,但测试质量依赖于prompt质量。未来可以让agent在运行测试后自动分析失败原因,自主决定是修复代码还是修复测试(但”不退化红线”原则会约束后者)。
更智能的基线管理:当前的基线是”总数不降”,但不同测试的权重不同。核心路径的测试价值远高于边缘功能。未来可以引入”加权基线”——核心测试必须过,边缘测试可以有条件退化。
UAT自动化:当前的UAT核验有不少手动操作。未来可以结合视觉回归工具(如Percy、Chromatic)和API契约测试工具,把UAT的一部分自动化,让agent在执行UAT时更独立。
flaky测试的自动识别与治理:当前flaky测试的治理依赖人工发现。未来agent可以在多次运行中自动标记flaky测试,分析flakiness来源,甚至自动修复。
测试覆盖率的可视化:当前测试基线是一维的(通过数量),缺乏覆盖率维度的可视化。未来可以生成”代码变更 → 测试覆盖”的映射图,让每个改动对应的测试覆盖一目了然。
小结
编码准确性不是某一项技术,而是四层防线相互嵌套的系统工程:
- TDD红线保证每个函数的正确性——测试不通过,代码不进仓库
- 版本控制约定保证每次变更的可回退性——出问题立即回退
- UAT核验清单保证端到端的行为一致——spec写了什么就验证什么
- 测试基线维护保证版本之间的非退化——新版本不比老版本差
对比三种方案:方案A(无测试)虽然快但最危险;方案B(人工事后测试)覆盖不可控且测试与代码不同步;方案C(TDD + 基线 + UAT)最可靠但开发速度最慢。aptbot选择方案C,并将质量约束工具化、红线化,让agent在系统层面无法绕过质量门。
这四层防线叠加的效果是:AI代码的bug被发现得越早,修复成本越低。TDD在编码阶段发现的问题,一秒钟就能修;UAT在发布前发现的问题,一天能修;线上用户发现的问题,可能要一周才能定位和修复。质量防线的本质不是”不出bug”,而是让bug出现在代价最低的时刻。下一篇文章,我们探讨支撑这些流程和质量的底层基础设施——spec文档的全生命周期管理。