AI 写代码越快,越要小步迭代、自动测试和持续重构
第四天上午,我没有继续堆商城功能,而是让同学们做一个很小的 FizzBuzz 页面。规则从 3 和 5 的倍数开始,然后不断加入“包含某个数字”“优先级变化”等新要求。
代码规模很小,课堂却很快出现了真实项目的典型问题:需求一句话说不清、规则互相冲突、每改一次都要重新手测、计算逻辑和页面混在一起。它正好揭示了 AI Coding 的另一面——代码生成越快,腐化也可能越快。
小步迭代是控制复杂度的方法
一次让 AI 完成十个功能,会同时带来十组需求假设、代码变化和潜在错误。即使最后页面能打开,开发者也很难判断哪次修改影响了什么。
更稳妥的节奏是:
- 只选择一个边界清楚的功能。
- 先写实例化验收标准。
- 实现并验证正常与异常场景。
- 自动测试通过后提交代码。
- 再开始下一个功能。
每一步都有可运行结果,也有明确回退点。
一句话需求为什么不够
“如果数字包含 3,就输出 base”仍然留下很多问题:包含规则和倍数规则谁优先?33、30、15 分别输出什么?输入不是整数时怎样处理?
验收标准必须使用具体输入输出消除歧义,例如:
- 输入 3,输出 base。
- 输入 13,输出 base。
- 输入 15,如果包含规则优先,也输出 base。
- 输入 10,按倍数规则输出 buzz。
实例不是为了凑测试数量,而是把业务人员脑中的判断变成开发、测试和 AI 都能共享的事实。
自动化测试让变化变得可承受
规则只有两三条时,手工验证尚可。一旦扩展到十几条,每次修改都重新输入所有数字,既枯燥又容易遗漏。
自动化单元测试把输入输出保存为可重复检查的样例。修改代码后运行一次,就能看到新规则是否破坏旧行为。
需要区分两类验证:Agent 临时打开页面、观察一次结果,是探索性检查;保存在仓库中、能通过固定命令重复运行的测试,才是下一次修改仍可依赖的工程资产。前者可以帮助定位问题,不能替代后者。
一条清楚的自动化测试通常包含“准备、执行、验证”三段:准备输入和受控数据,执行一个明确行为,验证对用户或接口有意义的结果。写在一个不透明循环里的几十个断言并不会自动更好;表格驱动或参数化测试只有在每组输入输出一眼可读时,才真正降低重复。
需要特别纠正的是:Vitest 不是 Node.js 自带测试框架,而是需要作为开发依赖安装的、由 Vite 驱动的测试框架。选择什么工具可以根据项目决定,关键是测试能够自动、稳定、快速运行。
对于大量相似用例,可以使用参数化测试减少重复,让测试数据比测试代码更醒目。
测试和重构的正确顺序
当计算逻辑直接写在 HTML 事件里,其他页面无法复用,也很难独立测试。重构可以把计算函数提取到单独模块,让页面只负责输入和展示。
但不要在没有保护的情况下大改结构。更安全的顺序是:
- 先补齐当前行为的自动化测试。
- 确认测试在重构前全部通过。
- 只改变内部结构,不改变外部行为。
- 每一步都运行测试。
- 测试持续通过后,再提交重构。
重构的核心是职责分离和可理解性,复用只是可能出现的收益。没有测试的重构,很容易变成一次赌博。
单元、API 和 UI 测试怎样分层
单元测试验证纯逻辑,速度快、定位准;API 测试验证接口、权限、数据库和错误响应;UI 测试验证用户在浏览器里的关键主流程。
三层不应该互相替代:
- 大量规则组合放在单元测试。
- 注册、登录、订单等服务边界放在 API 测试。
- 用户真正依赖的冒烟链路放在 UI 测试。
如果所有场景都依赖浏览器测试,执行慢且定位困难;如果只有单元测试,页面与真实服务仍可能无法协作。
课堂里的简单 FizzBuzz 页面只保留一条 UI 冒烟测试,是因为页面行为单一,而不是“所有页面只该写一条测试”。真实项目应根据风险选择关键行为:UI 证明用户能走通,API 证明契约、权限和数据边界,单元测试覆盖复杂规则。测试价值来自能否拦住重要回归,不来自测试数量。
测试也应该靠近变化点。改纯计算规则先跑单元测试,改接口或数据库边界先跑 API 测试,改交互流程再跑相关 UI 用例,最后执行全量回归。这样反馈更快,失败也更容易解释。
AI 时代开发者最不能丢的能力
至少要能读懂核心代码、理解错误发生在哪一层,并判断修改是否符合需求。
如果开发者只会继续输入“帮我修复”,Agent 可能不断叠加条件、复制逻辑、引入新依赖。短期功能似乎完成,长期却形成超长函数、死代码和不敢修改的模块。
自动化测试保证外部行为,持续重构保证内部结构。两者一起,才让 AI 的速度变成可持续产能。
收官检查清单
- 每次只开发一个可独立验收的功能。
- 验收标准包含具体输入、动作和预期结果。
- 旧用例在需求变化后仍会自动回归。
- 业务逻辑与页面、数据访问职责分离。
- 测试失败时能定位到单元、接口或 UI 层。
- 重构前有测试,重构后行为不变。
- 开发者能解释核心实现,而不是完全依赖 AI 描述。
讲师判断:速度红利必须兑换成更短的反馈周期
AI 把一个功能从两天缩短到两小时,不代表应该在两小时里堆十个功能。更好的做法是把节省下来的时间用于更早验收、更密集回归和更频繁重构。每次提交都保持范围小、状态可运行,问题出现时才能快速定位到最近一轮变化。
我会要求学生把“完成”的证据写进提交:对应哪条验收、运行了哪些测试、手工检查了哪个流程、仍有哪些已知限制。当 AI 生成速度继续提高,这套证据链会比代码量更重要,因为它让团队始终知道系统当前能做什么、不能做什么,以及下一步为什么值得做。
课后练习
为现有项目选一个条件分支最多的函数,先把当前行为写成参数化测试,再让 AI 在不改变行为的前提下拆分职责。比较重构前后的可读性,并故意改坏一条规则,确认测试能否立刻发现。