AI 写代码又快又好?你可能少了最关键的一步
AI 写代码又快又好?你可能少了最关键的一步
AI 10 分钟写完一个 CRUD 模块,编译通过,启动正常,上线一周后出了 bug——某个边界条件没人测过,AI 也没写测试。
目录
- AI-coding 的工程化问题
- 测试全景:不只是找 Bug
- TDD:先写测试,再写代码
- SDD:两种含义,同一个目标
- OpenSpec 和 Superpowers 如何内建测试
- 给 AI-coding 开发者的落地建议
AI-coding 的工程化问题
大多数人用 AI 写代码的流程是这样的:
- 把需求丢给 AI
- AI 吐出一堆代码
- 自己跑一下,能启动就算成功
- 上线
这个流程最大的问题不是 AI 写得不好,而是是是没有质量关卡是是。AI 产出的代码碰巧能跑,但没人验证它在边界条件下是否正确,也没人确认它和其他模块拼在一起会不会炸。
我之前用 Claude Code 开发一个客户管理模块,10 个任务全部用子代理并行跑完,编译全过,看起来很顺利。结果部署时才发现:SQL 脚本的列数对不上、JAR 包没重新打包、一个字段忘了赋值。这些问题在编译通过的表象下藏了好几天。
这就是 AI-coding 的工程化问题:::AI 能写代码,但不会替你做工程。::
目前有两个工具在尝试解决这个问题:
- OpenSpec :规格驱动开发框架。核心理念是先想清楚再写代码——需求探索 -> 生成提案 -> 执行编码 -> 质量验证 -> 归档沉淀。它管的是做什么。
- Superpowers :Claude Code 的插件,把软件工程最佳实践固化成 AI 可遵循的工作流。核心理念是 Process over Prompt——流程大于提示词。它管的是怎么做。
两者都指向同一个被忽略的环节:::测试::。
测试全景:不只是找 Bug
很多人对测试的理解停留在找 Bug。但测试远不止于此——它是一门涉及质量评估、风险控制与成本管理的系统性工程。
测试金字塔
经典的测试策略遵循金字塔结构:
- 底层(60-70%):单元测试 。针对单个函数或方法,隔离外部依赖,运行速度极快(毫秒级)。数量最多,是基石。
- 中层(20%):集成测试 。验证模块之间的交互——API 调用、数据库读写、消息队列。会连接真实资源,速度较慢。
- 顶层(10%):端到端测试(E2E) 。模拟真实用户操作,成本最高、速度最慢、维护最贵。
对 AI-coding 来说,单元测试是第一道防线。AI 写的代码逻辑可能对,也可能不对,单元测试能在毫秒级告诉你答案。
单元测试 vs 集成测试
| 维度 | 单元测试 | 集成测试 |
|---|---|---|
| 测试对象 | 单个函数/方法/类 | 多个模块之间的接口与交互 |
| 外部依赖 | 全部模拟(Mock) | 尽量真实,连接数据库/网络 |
| 运行速度 | 极快(毫秒级) | 较慢(秒级甚至分钟级) |
| 数量 | 数量庞大(成千上万) | 数量较少(对应核心业务流程) |
| 定位 Bug | 精确,立刻知道哪个函数错了 | 模糊,只知交互失败,需排查两端 |
一个实用原则:如果 Bug 在集成测试中被发现,先补一个单元测试来复现它,修复后再跑集成测试。这能确保同一个 Bug 永不复发。
测试四原则
- 尽早测试(Shift-Left) :测试不应等编码结束才开始,需求分析阶段就能发现问题。越早修,成本越低。
- 杀虫剂悖论 :总用相同的测试用例,系统会对这些 Bug 免疫。必须定期更新测试用例。
- 缺陷集群性 :80% 的崩溃集中在 20% 的核心模块。测试资源应重点轰炸复杂度最高的区域。
- 上下文依赖 :没有万能测试方案。银行支付系统的测试强度不能照搬企业内部 OA。
为什么 AI-coding 更需要测试
AI 不理解业务意图,只理解模式匹配。它会写出看起来正确但逻辑有漏洞的代码——因为它是在模仿训练数据中的模式,而不是在理解你的业务规则。
测试的作用不是验证 AI 写对了什么,而是是是告诉 AI 应该写什么是是。当你先写好测试用例,AI 的行为边界就被框死了:它必须产出能通过这些测试的代码。这就是 TDD 的核心价值。
TDD:先写测试,再写代码
TDD(Test-Driven Development,测试驱动开发)是一种测试先行的开发方法论。它的核心信条:::在编写任何生产代码之前,先编写一个会失败的测试用例。::
红-绿-重构
TDD 严格遵循一个极短循环:
- 红(Red) :先写一个测试,调用你尚未实现的函数。运行测试,它必然失败。目的:确认测试有效,同时明确当前任务边界。
- 绿(Green) :写刚好能让测试通过的代码。不要考虑优雅,唯一目标是让测试变绿。目的:尽快获得可工作的状态。
- 重构(Refactor) :测试通过后,放心优化代码结构。重构过程中时刻运行测试,改坏了立刻知道。
每次循环通常只持续 1-5 分钟,小步快跑。
TDD 在 AI-coding 中的价值
用 AI 写代码时,TDD 的意义变了。传统开发中,TDD 是为了驱动设计;在 AI-coding 中,TDD 是为了了了约束 AI 的行为边界了了。
想象一下:你让 AI 写一个客户导出功能。如果不先写测试,AI 可能给你一个能跑但缺少边界处理的实现——比如没考虑空数据、没考虑特殊字符、没考虑权限校验。你得自己一个个发现这些问题。
但如果你先写好测试:
- 导出空数据时应该返回空文件而不是报错
- 客户名称包含逗号时 CSV 应该正确转义
- 没有导出权限的用户应该被拒绝
AI 就必须产出能通过这些测试的代码。测试用例变成了 AI 的行为规范。
真实教训:TDD 缺失的代价
我在用 Superpowers 开发客户管理模块时,10 个任务全部跳过了 TDD——先写代码,后补测试。结果:
- 边界条件遗漏:测试没有驱动设计,某些异常路径根本没覆盖到
- 兼容性问题发现太晚:Spring Boot 4.0.6 移除了 @WebMvcTest 和 @MockBean,直到补测试时才发现
- 测试变成了验证而非定义:后补的测试只是在确认现有代码能跑,而不是定义代码应该怎样跑
事后补测试不等于 TDD。TDD 的精髓是测试先行——测试用例定义了期望行为,代码去满足这个期望。顺序反了,效果就完全不同。
SDD:两种含义,同一个目标
SDD 在 AI 编程领域至少有两个含义,它们指向同一个目标 : 让开发过程有章可循
Spec-Driven Development(规范驱动开发)
如果说 TDD 是用测试用例驱动开发,SDD 就是用规范文档驱动开发。它主张在写任何代码之前,先用结构化的自然语言把软件要做什么、为什么做清晰定义出来。
核心工作流:
- 指定(Specify) :定义用户故事、验收标准
- 计划(Plan) :确定技术架构和实现方法
- 任务(Task) :将计划拆解成具体的开发任务
- 实现(Implement) :根据规范和计划编写代码
OpenSpec 就是这个理念的工程化实现。它的 spec.md 文件用 SHALL/MUST 这样的正式语言定义系统行为,每个 Scenario 都是一个可验证的行为契约。
Subagent-Driven Development(子代理驱动开发)
这是 Superpowers 插件里的 SDD。它的工作方式是为计划中的每一个任务都启动一个全新的、独立的子代理去执行。每个任务在干净的环境中进行,避免不同任务间的上下文污染。
任务完成后还有两阶段评审:先检查是否符合规范,再检查代码质量。
SDD 和 TDD 的关系
它们不是互斥的,而是互补的:
- SDD 管做什么 :需求 -> 规格 -> 设计 -> 任务清单
- TDD 管做对没 :每个任务内部,RED -> GREEN -> REFACTOR
一个完整的开发流程应该是:先用 SDD 写好规范,再用 TDD 保证代码质量。
OpenSpec 的 spec.md 中每个 Scenario 可以直接映射成测试用例。比如 spec 里写了按筛选条件导出和无筛选条件导出两个场景,那就是两个测试用例的骨架。
OpenSpec 和 Superpowers 如何内建测试
OpenSpec 的测试集成
OpenSpec 在三个地方把测试嵌入了开发流程:
- spec.md 的 Scenario :每个需求下面的 Scenario 天然就是测试用例。写 spec 的时候就在写测试骨架。
- tasks.md 的 TDD 阶段 :任务清单的第一组任务就是 TDD Red Phase——先写失败测试。
- Verify 阶段 :编码完成后,跑测试验收。测试不过,不允许归档。
这种设计让测试不是一个独立的活动,而是开发流程的有机组成部分。
Superpowers 的测试集成
Superpowers 通过 test-driven-development skill 强制 AI 先写测试。如果 AI 在没有先编写测试的情况下就开始写实现代码,Superpowers 会直接打断并阻止它。
在子代理驱动开发流程中,每个任务应该遵循:RED(写失败测试)-> GREEN(写实现)-> REFACTOR(优化)-> 提交。审查子代理还会验证测试是否先于实现。
但实际执行时,这个流程经常被跳过。我在复盘中发现,所有 10 个实现任务都是先写代码后补测试,审查子代理也主要做逐行对比规范,没有深入检查测试覆盖。
两者对比
| 维度 | OpenSpec | Superpowers |
|---|---|---|
| 测试来源 | spec.md 的 Scenario 自动映射 | 需要手动编写或由 TDD skill 驱动 |
| 强制程度 | Verify 阶段卡关 | TDD skill 可被跳过 |
| 测试范围 | 偏向验收级别(场景驱动) | 偏向单元级别(代码驱动) |
| 适用阶段 | 需求定义阶段就开始 | 编码执行阶段才介入 |
理想状态:用 OpenSpec 的 spec 定义验收场景,用 Superpowers 的 TDD skill 驱动单元测试编写。两者配合,测试覆盖面最全。
给 AI-coding 开发者的落地建议
1. 用 spec.md 写 Scenario,每个 Scenario 就是一个测试骨架
不管你用不用 OpenSpec,在动手写代码之前,先花 10 分钟列出这个功能的核心场景。每个场景用 WHEN/THEN 格式写一句话。这不只是文档,这是你后面写测试用例的骨架。
比如你要做一个客户导入功能:
- WHEN 用户上传了格式正确的 Excel THEN 系统导入成功并返回导入数量
- WHEN 用户上传了空文件 THEN 系统返回错误提示
- WHEN Excel 中有重复客户名 THEN 系统跳过重复项并报告
三个场景,三个测试用例,10 分钟搞定。
2. 让 AI 先写测试再写实现
如果你用 Superpowers,开启 TDD skill 即可。如果不用,也可以手动约束:每次给 AI 下需求时,加一句先写测试用例,确认测试失败后再写实现代码。
这个简单的约束能显著提升 AI 产出的代码质量。因为测试用例定义了边界,AI 就不会天马行空地自由发挥。
3. 集成测试不要跳过
AI 写的单元测试可能全绿,但模块拼起来会炸。原因很简单:单元测试用 Mock 隔离了外部依赖,但真实运行时模块之间是要互相调用的。
在每个功能模块开发完成后,跑一次集成测试——启动真实数据库,调真实 API,验证数据流转是否正确。这比部署后才发现问题要便宜得多。
不需要一步到位。先从核心业务逻辑的单元测试开始,覆盖关键路径,再逐步补充集成测试。
写在最后
AI 写代码快不快不重要,重要的是写出来的代码经不经得起改。
没有测试的 AI-coding 就像没有安全带的赛车——直线跑得快,弯道翻得也快。TDD 给你安全带,SDD 给你导航,单元测试和集成测试给你双保险。
测试不是 AI-coding 的附加项,而是从玩具到工程的分水岭。
