OpenSpec+TDD实践:AI写代码的测试兜底方案
AI 写代码确实效率惊人,但你有没有问过它一个关键问题:“你写的代码真的正确吗?”它会自信地回答“全部通过”,可一运行,全是错误。这种表面上的完美,往往隐藏着最致命的隐患。下面这套方案,用 OpenSpec 来梳理和管理需求定义,用 TDD(测试驱动开发)来保证代码的正确性,两者在实际开发中串联使用,效果非常理想。

为什么必须这样组合
如今越来越多开发者在大量使用 AI 辅助编码,效率确实呈爆发式提升。但运行一段时间后,会察觉到一个令人不安的现象:AI 生成的代码看起来总是滴水不漏。
它不会主动报错,也不会说“这里我不确定”,写出的代码结构清晰、命名规范、注释齐全——简直就是模范代码。可一旦实际运行呢?边界情况被遗漏了,业务逻辑理解出现了偏差,有时甚至会编造一个根本不存在的 API,还自信满满地去调用。你问它“问题解决了吗”,它回答“全部检测通过已经完成”,其实连编译都通不过。
这种现象被称为 AI 的“自洽幻觉”:它总能给出一个看似“合理”的答案,但合理从来不代表正确。
因此核心问题变成了:在代码生成成本越来越低的今天,正确性才是真正稀缺的资源。谁来保障正确性?测试。注意,不是写完代码后补的那种测试,而是 TDD 那种——先写测试,再让 AI 去实现。
但仅有 TDD 还不够。测试能告诉你“代码能不能跑对”,却无法告诉你“需求本身拆分得是否合理”。如果需求拆分歪了,即使所有测试都绿,也是白费功夫。
这就是 OpenSpec 的定位:它负责“做什么”,TDD 负责“做对没”。
整体流程概览
两者的衔接点落在 Apply 阶段。前期用 OpenSpec 把意图理清楚、把方案定下来,到了动手环节,每个 task 内部走 TDD 循环。
Explore → Propose → Apply (TDD循环) → Archive
│ │ │ │
│思考讨论 │proposal.md│ Red→Green→Refactor│归档 + sync specs
│明确意图 │design.md │ 逐task推进 │
│ │tasks.md │ │
│ │specs/ │ │
四个阶段各司其职,但并不是僵化的瀑布流——你随时可以从 Apply 退回 Propose 去修改设计,也可以跳过 Explore 直接开始。关键在于每个阶段解决不同的问题。
Tasks 的正确写法
这是整个方案中最关键的一环。常见的 task 写法是“实现用户登录功能”——这种粒度对 AI 来说过于粗糙,它会一口气把登录、注册、token 生成全部写完,测试?根本不存在。
正确做法是将测试 task 和实现 task 成对出现:
## Tasks
- [ ] 为用户登录写失败路径测试(无效密码、不存在用户)
- [ ] 实现登录逻辑使测试通过
- [ ] 为token生成写测试(过期、签名验证)
- [ ] 实现token生成使测试通过
- [ ] 重构:提取认证中间件
- [ ] 为中间件写集成测试
- [ ] 实现中间件使测试通过
先测试、再实现、中间穿插 refactor。这并非形式主义——这个顺序决定了 AI 在 Apply 阶段的行为模式。如果你不显式写出测试 task,AI 会直接跳到实现,然后补上一堆 happy path 测试来交差。
Apply 阶段的 TDD 节奏
每个 task 内部遵循经典的 Red-Green-Refactor 循环:
单个 Task 的执行流程:
1. Red → 写测试,运行,确认失败
2. Green → 写最少代码让测试通过
3. Refactor → 清理代码,测试仍通过
4. ✓ 标记task完成
有一个细节特别容易被忽略:第 1 步的“确认失败”非常关键。假如你一上来测试就绿了,那说明两种情况——要么这个功能已经实现了(那这个 task 就是多余的),要么你的测试写得有问题(没测到关键点上)。无论哪种情况,都必须停下来仔细分析。
“写最少代码”也有讲究。AI 特别喜欢过度实现——你让它通过一个测试,它顺手把后面三个 task 的功能也写了。此时需要在 prompt 里明确约束:只让当前这一个测试变绿,不要多做。
实际操作完整演示
拿“给系统加用户认证”这个需求举例:
# 1. Explore - 理清要做什么
/opsx:explore add-user-auth
# 讨论:用JWT还是session?是否需要第三方登录?密码策略如何制定?
# 2. Propose - 生成方案(task按TDD节奏拆分)
/opsx:propose add-user-auth
# 产出 proposal.md / design.md / tasks.md / specs
# 3. Apply - 逐个task实施
/opsx:apply add-user-auth
Apply 过程中,AI 的实际行为演示:
// Task: "为用户登录写失败路径测试"
// AI写出:
@Test
fun `login with invalid password returns error`() = runTest {
val testUser = userRepository.createUser("test@example.com", "correct_password")
val result = loginUseCase.execute("test@example.com", "wrong_password")
assertThat(result).isInstanceOf(LoginResult.Failure::class.ja va)
assertThat((result as LoginResult.Failure).reason).isEqualTo("invalid_credentials")
}
@Test
fun `login with nonexistent user returns error`() = runTest {
val result = loginUseCase.execute("nobody@example.com", "any_password")
assertThat(result).isInstanceOf(LoginResult.Failure::class.ja va)
}
// → 运行测试 → RED ✗(因为LoginUseCase尚未实现)
// → 标记为已完成 [x]
// Task: "实现登录逻辑使测试通过"
// AI写出:
class LoginUseCase @Inject constructor(
private val userRepository: UserRepository,
private val tokenManager: TokenManager
) {
suspend fun execute(email: String, password: String): LoginResult {
val user = userRepository.findByEmail(email) ?: return LoginResult.Failure("invalid_credentials")
if (!user.verifyPassword(password)) {
return LoginResult.Failure("invalid_credentials")
}
val token = tokenManager.createAccessToken(user.id)
return LoginResult.Success(token)
}
}
// → 运行测试 → GREEN ✓
// → 标记为已完成 [x]
# 4. 所有task完成后归档
/opsx:archive add-user-auth
Spec 与 Test 的关系
这两者的关系容易混淆,用一个类比说清楚:Spec 是合同,Test 是验收。
合同写的是“用户连续5次登录失败应锁定账户”,这是业务语言,给人看的。验收做的是 account locks after 5 failures 这个测试用例,这是代码语言,给机器跑的。
openspec/specs/auth/spec.md ← 合同(业务语言)
“用户连续5次登录失败应锁定账户”
app/src/test/ja va/.../auth/LoginUseCaseTest.kt ← 验收(代码语言)
fun `account locks after 5 consecutive failures`()
每一条 spec 都应该能映射到至少一个测试——如果有一条 spec 找不到对应的测试,要么是测试遗漏了,要么是这条 spec 写得过于空泛(比如“系统应该安全”这种就无法直接测试)。
反过来也成立:如果你写了一个测试却找不到对应的 spec,说明你在测试一个没有被明确定义过的行为。这种测试不是不能存在,但需要思考它是否应该先变成一条 spec。
推荐 config.yaml 配置
schema: spec-driven
context: |
开发模式:OpenSpec + TDD
测试框架:JUnit5 + Truth + Mockk
# Android项目常用组合
测试目录:app/src/test/ 和 app/src/androidTest/
每个task必须先有测试覆盖再写实现
rules:
tasks:
- 测试类task和实现类task必须成对出现
- 每个实现task之前必须有对应的测试task
- Refactor task的前提是所有测试通过
design:
- 设计中需包含testability考虑
- 标注哪些边界需要mock/stub
这个配置的作用是约束 AI 在 Propose 阶段生成 tasks 时严格遵循 TDD 节奏。如果没有这些 rules,AI 很容易生成“实现 XX 功能”这样笼统的 task,导致测试被完全忽略。
这套方案实际使用下来,最直观的体会不是代码质量大幅提升,而是终于可以在 AI 写完代码之后安心去喝杯咖啡了。测试守住了底线,spec 锁住了意图,即使 AI 出现幻觉,也逃不开这个圈。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
阿里云OpenClaw官方镜像六大场景3分钟开箱即用指南
先聊聊OpenClaw到底是什么,以及它为什么值得关注。作为阿里云推出的智能助理平台,OpenClaw基于通义千问大模型深度定制,目标很明确:为开发者、创作者、运营者提供一站式的AI赋能解决方案。下面直接切入正题,看看它的六大核心场景。 OpenClaw 智能助理:六大核心场景赋能开发者高效成长 O
Moltbot Clawdbot与飞书机器人接入实践
简单认识一下 Clawdbot 最近 AI 圈被一款名为 Clawdbot 的产品刷屏了。不管是在国内技术社区,还是刷 TG、X 的时候,几乎都能看到有人在讨论它。 看了一下官方文档,Clawdbot 本质上就是一个偏“个人智能助手”的东西。不过它并不是单独开一个网页给我们用,而是可以直接接入我们平
SpringAI与ONNX打造免费离线向量引擎
前段时间尝试了一个很有意思的项目——原本只是想在 Spring AI 项目中顺手集成 ONNX 模型,结果一上手就停不下来,直接调试到凌晨两点,边调边感慨:整个过程也太丝滑流畅了。 今天就来深入聊聊这件事:如何在 Spring AI 中使用 ONNX 向量模型,实现本地化的文本嵌入能力。 如果你之前
AI智能体技能完全指南:让你的AI助手拥有超能力
引言:AI Agent 的能力边界在哪里?你的AI编程助手可以编写代码,但它是否真正理解你公司的独特工作流程?能否自动处理你的CI CD流水线?又是否熟悉你日常使用的那些特定工具与API接口?AI Agent Skills正是为解决这一痛点而诞生的——它们作为可复用的能力模块,能够将通用型AI助手转
AI编程神器狂揽34k星与Claude Code和Codex绝配
CC Switch:一站式AI编程工具管理神器 今天要介绍的这款实用小工具,名字叫作CC Switch。它是一款跨平台的桌面“All-in-One”助手,专门用于管理主流的AI编程开发工具。目前该项目在GitHub上已经获得了34k+ star,关注度非常高。它的核心卖点很直接:提供一个可视化操作界
- 日榜
- 周榜
- 月榜
相关攻略
2026-06-06 18:43
2026-06-06 18:40
2026-06-06 18:40
2026-06-06 18:39
2026-06-06 18:39
2026-06-06 18:39
2026-06-06 18:39
2026-06-06 18:39
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

