SDD+TDD的探讨

最近用Codex写代码上头了,感觉离开Codex一天自己都活不下去。

但是作为一个小白转码的新人,在 Vibe Coding 的过程中,总会有 Agent 生成代码太长,中间如果出 bug 了不好调试的担忧。在解决问题的过程中,就发现了目前程序员在用 Agent 写代码时常用的一种 SDD + TDD 方法,我就来总结一下,顺便加深一下自己的理解。

核心内容

  • SDD + TDD 的配合

  • 用什么工具:Coding Agent 选型

  • 从头走一遍:spec → 后端 → 契约 → 前端

SDD + TDD 的配合

SDD(规格驱动开发)是什么

SDD,即规格驱动开发(Spec-Driven Development),核心思路一句话:规格是首要产物,代码是从规格构建出来的。

传统开发流程里,需求文档写完,开发就开始写代码。规格——如果写的话——往往夹在需求文档的段落里,或者散落在各个接口文档中,不是独立的一级产物。SDD 把这个顺序倒了过来:在动手写任何实现代码之前,先把”做成什么样算对”写成一份独立、完整、人能审的规格,审过之后,代码从规格生成。

规格阶段有一个重要的设计原则:聚焦”怎么用”,不急于讨论”怎么实现”。 好的 spec 讨论的是场景——在什么情况下、解决什么问题、输入和输出是什么。它不讨论用什么框架、数据库怎么设计、服务怎么拆分。

以设计一个待办事项 API 为例,spec 阶段应该问的是:用户创建待办时要不要填截止日期?标记完成之后还能不能撤销?列表默认按什么排序?——这些是”怎么用”的问题。后端用 FastAPI 还是 Flask、数据存内存还是 SQLite,这些是实现方案的范畴,在 spec 确认之后再回答。

同样的道理,在设计一个框架的时候,第一件事不是画架构图,是先写出预期的调用代码样例——这段代码读起来是不是顺畅、使用者需要写多少行才能完成一个常见任务。代码样例确认了”易用”,内部的实现方案是后续顺水推舟的事情。这就是 spec 阶段的核心价值:用最轻的方式把”怎么用”聊清楚、定下来,之后的实现方案和测试用例都是自然推导。

这份规格通常包含:数据模型、接口的输入和输出、校验规则、边界条件、错误返回,以及明确不做的事。它的核心特征是人可以快速审完——不是上百页的文档,而是十几条可以逐条对照的约束。

SDD 这个方向在 2025 年前后加速成型,直接原因是 AI 编码工具的普及。当代码的”写”越来越快、越来越便宜,”写得对不对”就成了瓶颈。GitHub 在 2025 年 9 月开源了 Spec Kit(specify → plan → tasks → implement),AWS 在 2025 年 7 月发布了 Kiro(需求 → 设计 → 任务 → 实现)。它们面向的是同一个问题:AI 生成的代码怎么从凭感觉走向可验证。

TDD(测试驱动开发)是什么

TDD,即测试驱动开发(Test-Driven Development),是 Kent Beck 在 1990 年代末系统化提出的一套开发方法。核心操作是一个短循环:**先写一个会失败的测试(红),再写刚好能让它通过的最少实现(绿),然后清理代码(重构)。**循环一次只走一个小步,每一步都有测试兜底。

TDD 在提出后的二十多年里,认同的人多、坚持的人少。原因很简单:手写测试太慢了。在快速迭代的项目里,测试经常是第一个被砍的。

AI 把这个账重新算了一遍。AI 写测试初稿的速度比人快得多——原来一个下午才能写完的测试,现在十分钟就能生成。而且测试恰好是 AI 生成代码最缺的东西:一个不带感觉、只判断对错的裁判。所以在 AI 编码的时代,TDD 从”道理对但太慢”变成了”道理对且现在做得起了”。

但这里不能把”AI 能生成测试”误解成”测试就一定可信”。测试本身也需要审:它有没有覆盖 spec 的关键约束,有没有只测 happy path,有没有把错误行为写成了正确预期。AI 可以把写测试的体力活降下来,但测试是否真的代表需求,仍然要人把关。

这里值得补一个判断。TDD 这套方法本质上是反人性的——它要求人在写实现之前先写一堆会失败的测试,把满足感最强、收益最直接的”先把功能跑起来”往后推。人会嫌麻烦、会偷懒,本能地先做那些能立刻看到成果的事,测试于是一拖再拖。

但同样这套约束,放到 AI Agent 身上就刚刚好。AI Agent 不会嫌麻烦,也不在乎满足感来得早还是晚,它真正需要的恰恰是 TDD 能给的两样东西:明确的行为指导(规格和测试告诉它要做成什么样),和清晰的检验标准(测试的红绿告诉它做到了没有)。对人来说是负担的东西,对 AI 来说是它最需要的脚手架。从这个角度看,TDD 在 AI 时代的复活,不只是因为写测试变快了,更是因为执行这套方法的主体——从一个会偷懒的人,换成了一个需要明确指令才能干好活的 Agent。

SDD 与 TDD 怎么配合

两个概念经常被放在一起提,但它们管的是不同的事:

  • SDD 回答”什么是对”:规格是人确认的,描述的是”做成什么样算对”
  • TDD 把”对”变成机器能判的东西:规格的每一条规则和边界,变成一条可执行的测试

规格是源头,测试是规格的可执行形态。两者配合的流程是:

  1. 先写规格(SDD),人审过
  2. 按规格写测试(TDD 的红),新增或变更行为对应的测试应该先红
  3. 补实现跑到全绿(TDD 的绿),测试没绿就继续修

这里有一个容易误解的地方:如果是从零开始的新模块,”没有实现,全红”通常成立;但在已有项目里,可能已经有一部分旧实现能通过测试。真正要确认的是:新增行为或变更行为对应的测试,应该先因为预期原因失败,而不是一写出来就绿。

这套流程走到”全绿”,代码就算是有了第一层保障。具体到 Python 技术栈,验收可以分成三层:

  • 第一层:pyright 静态校验。 类型错误、缺少导入、参数不匹配——这些不需要跑测试,静态分析就能拦下来。每次 AI 产出代码之后,先跑 pyright,零 error 再往下走
  • 第二层:pytest 全量用例。 这是 TDD 的主体——spec 的每一条规则和边界都变成一条测试,全绿才是”行为正确”
  • 第三层:example 实际案例 + 人工复检。 前两层是机器判的,但有些东西机器判不了——实现方案是不是好用(调用方式是否简洁、错误提示是否清晰)、业务逻辑是不是真的正确(case 覆盖的语义对不对)。这一层要人来看,关注的是易用性和业务正确性

三层下来,效果是把一个概率系统(LLM)约束到可接受区间:

SDD 定义“什么是对”,TDD 把“对”变成机器能判,一起把概率产出夹回可接受区间

SDD 的操作可以总结成三步——有人把它叫作”Spec Coding 三铁律”。”铁律”这个说法偏重,但三步的顺序值得记住:先写规格、规格必须可验证、小步可回滚。

先写规格——在让 AI 写实现之前,先把输入、输出、边界、校验规则、明确不做的范围写成一份人能审完的文档。审规格审的是”对不对”,不是”看着像不像对的”。

规格必须可验证——每一条规则都要能变成一条测试或一个类型约束。不可验证的规格等于没有——“性能不能太差”是不可验证的,”P99 延迟不超过 200ms”才是。

小步可回滚——每次只改一件能独立验证的事。一个端点一个红绿循环,不是一次性把整个服务写完。每步的爆炸半径控制在一个端点以内。

行业里已经有一些具体形态在往这个方向走。GitHub Spec Kit 把流程固化成 specify → plan → tasks → implement,AWS Kiro 内置了需求 → 设计 → 任务 → 实现四个阶段。

coding agent 选型

先说明一下:这一节更像个人使用体验,不是客观排行榜。AI 工具迭代很快,价格、可用地区、模型能力和试用政策都可能变化,真正选型前最好再看一眼官方最新说明。

维度 Codex Claude Code 原厂 CC + DeepSeek GitHub Copilot
复杂任务 / spec 强遵从 最好 可能有偏差 不推荐
自主探索能力 最强
UI 审美 / 文科类任务
中文文案语言顺畅度 好很多
成本 较高 较高 便宜
速度 耗时长
整体交付质量 可以(够用) 低端平替

Codex:复杂任务、强 spec 遵从场景下的首选体验。在需要严格跟着 spec 走的场景里(spec→test→实现),偏离度低。

Claude Code 原厂:自主探索能力最强,UI 审美和文科类任务表现好。适合需要 AI 主动探索、自由发挥的场景。

CC + DeepSeek:性价比路线。复杂任务和 spec 遵从比原厂略有偏差,耗时长一些,但整体交付质量够用。便宜,而且中文文案的顺畅度明显更好。

GitHub Copilot:以前是低端平替,现在个人不推荐。尤其是个人付费计划、试用和地区可用性这类政策变化很快,选型前一定要复核官方最新说明。

从头走一遍:spec → 后端 → 契约 → 前端

步骤 1:spec 探讨


Spec 模板(SDD 用)

用法:动手让 AI 写代码前,先和它一起把这张表填出来,你审一遍、改两处,再让它写。
你审的是这张表(十几行),不是它写的实现(上百行)。

一句话目标

这个东西要解决什么、做成什么样算对。

数据模型
字段 类型 约束(必填?范围?默认?)
端点 / 接口
动作 输入 成功返回 失败情况(状态码)
边界(每条之后会变成一条测试)
  • 空 / 极值 / 重复 / 不存在 / 冲突……
  • 权限:谁能做、谁不能做?
  • 幂等:重复提交会不会造成重复数据或重复副作用?
  • 并发:两个请求同时发生时,最终状态怎么定?
  • 性能:有没有明确的响应时间或数据规模目标?
明确不做(划出范围,防 AI 自由发挥)

-

上线约束(生产风险提前说清)
  • 是否涉及删数据、动钱、发通知、对外发布、改线上配置?
  • 是否需要日志、审计记录或指标监控?
  • 是否需要数据迁移、兼容旧数据或回滚方案?

三条自检:①每条规则能不能写成测试?不能就是没说清。②人能在 2 分钟内审完吗?③范围划死了吗?

步骤 2:TDD 红——按规格只写测试

把确认后的 spec 喂给 AI,要求它只写测试、不写实现。需要明确几点:

  • 按 spec 写测试,不要写实现代码
  • 三个端点各至少一条正常路径的测试
  • spec”边界”小节的每一条对应一条测试
  • 每条测试尽量独立,不依赖前一条的数据库状态

跑一下——全是红的,因为没有实现。按照 spec 的边界数量,8 条测试全部 FAIL。

测试现在是退出标准:AI 得把这些红全部变成绿,才算完工。


测试先行清单(TDD 用)

让 AI 写实现前,先让它(按 spec)写测试。测试是 AI 的”退出标准”——红转绿才算完。

红 → 绿 → 重构,循环
  • :按 spec 的每条规则/边界写测试,新增或变更行为对应的测试应该先红
    • 正常路径每个端点至少 1 条
    • spec「边界」小节每条 1 条(空/极值/重复/不存在/冲突)
  • 绿:让 AI 补实现,跑到全绿
    • 中途有红:把失败的具体断言 + 输入喂回去,别只说”有 bug”
    • 不要为了全绿直接改测试;如果必须改测试,先说明原测试哪里不符合 spec
  • 重构:测试保持绿的前提下,让 AI 清理实现
一个端点一个循环

不要一次让 AI 写完整个服务。一个端点跑完红绿,再下一个——这就是”渐进式复杂度管理”。

验证”测试真的在兜底”

故意改坏一处实现 → 对应测试应该立刻变红。变不红,说明这条没被测住。


判断:你优先 review 的是 spec 和测试,不是先陷进实现细节。测试把审查成本前移、压缩了,但不能完全替代代码审查。

步骤 3:TDD 绿——补实现,红转绿

让 AI 补实现。一个端点一个端点来,不要一次堆给它——这刚好是对应”渐进式复杂度管理”的实操。

中间有一个操作值得单独说明。可以故意留一个边界让它先没处理,比如空白标题 " "(去掉首尾空白后其实是空的,但 AI 容易只判断了 "" 而漏掉 " "),对应的测试是红的。

这时候怎么告诉它?把失败的断言、输入和期望原样喂回去。例如:

test_blank_title_rejected_422 这个测试没通过。输入是 {"title": " "},期望返回 422 校验错误,但实际返回了 201。校验逻辑需要先 strip 再判空。

AI 拿到具体失败信息,一两轮就能定位修复。笼统说”有 bug”和给具体断言,修复效率差好几倍——前者 AI 得猜问题在哪,后者直接定位。最后全绿——8 passed。


关键观察

  • 审查成本被前移、被压缩。全程优先审的是 spec(人改的)和测试(机器生成的,能快速扫结构),不是一上来就逐行读上百行实现。AI 的实现是在测试的红绿之间自己迭代出来的——review 的重心从逐行找 bug,转向审 spec、审测试覆盖和抽查关键实现
  • 测试是退出标准。AI 不是”觉得写好了”,是机器判它过了。从红转绿的过程中,AI 从概率引擎变成了有明确 stop condition 的执行者
  • 渐进式复杂度。一个端点一个红绿循环——新建完成再写列出,列出完成再写完成标记。不是一次把三个端点全甩给它。每一步的爆炸半径在一个端点以内
  • 失败信息要结构化回喂。不是”fix the bug”,是把具体的断言、输入、期望和实际返回一起给。定位错误的时间远少于让它自己猜

这套流程容易失效的地方

SDD + TDD 不是银弹,它只是把风险显性化。最常见的失效点有几个:

  • AI 为了全绿去改测试。这是最危险的假绿。实现阶段如果要改测试,必须先说明原测试为什么不符合 spec,否则默认不允许改。
  • 测试只覆盖 happy path。创建成功、列表成功、标记成功都测了,但空输入、重复提交、越权访问、资源不存在、并发冲突没测,最后还是会在真实场景里炸。
  • mock 太重,测不到真实集成问题。单元测试全绿,但数据库约束、事务、序列化、鉴权中间件没有跑到。关键路径至少要有少量集成测试或端到端 example。
  • spec 本身写错了。测试忠实执行了错误规格,代码也全绿,但产品意图错了。这类问题只能靠人审 spec 和业务语义。
  • 非功能需求没进 spec。性能、日志、审计、安全、可观测性、回滚策略如果一开始没写,AI 通常不会自动补齐。

步骤 4:把契约喂给 AI 生成前端 UI

把后端跑起来后,FastAPI 自动生成的一份 OpenAPI 契约 openapi.json 提供给 AI,让它生成待办列表和新建表单的页面。生成完后,逐条看里面的约束从哪来的:

前端里看到的 来自契约哪一项
页面只调 /todos/todos/{id}/done 契约的 paths——总共就这些端点
新建请求体只发 { title } TodoCreate schema——只有 title 一个字段
标题输入框 maxlength="100" title.maxLength
标题为空不允许提交 title.minLength: 1

前端没有自己拍脑袋决定任何东西。所有约束全部从后端契约顺下来。AI 仍然可能犯错,但凭空编出后端不存在字段的概率会低很多;即便编了,也更容易被 typed client、schema 校验或人工 review 拦住。


方法卡:用 API 契约约束前端

题眼:API 定义不是文档,是上一层的产出变成下一层的规格。

链路
1
2
3
4
5
6
后端(spec→TDD 生成)
└─▶ 自动产出 OpenAPI 契约(/openapi.json)
├─ paths 端点:前端只能调这些
├─ schemas 请求/响应字段与类型:前端不能编不存在的字段
└─ min/maxLength 等约束:前端校验直接继承
└─▶ 把契约喂给 AI 生成 UI,约束自动落到代码
怎么做
  1. 后端跑起来,拿到 openapi.json
  2. 提示 AI:「按这份 OpenAPI 契约生成 UI,端点/字段/校验以契约为准,不要自造后端没有的字段。」
  3. 进阶:从 OpenAPI 直接生成 typed client / mock,把”约束”落到代码级别(前端在后端没好时也能照契约开发)。
为什么有用
  • 前后端的”真相源”是同一份契约,对不上的地方在生成时就被挡住,而不是联调时才发现。
  • 契约变了,前端跟着重新生成——确定性逐层传导,不是一锤子买卖。

关键观察

API 定义不是文档——是上一层的产出变成下一层的规格。

spec 沿技术栈向下游传导:spec 探讨→TDD红绿→后端→OpenAPI 契约→前端 UI

  • 后端产出的 OpenAPI,是前端的 spec
  • 前端从这个 spec 长出来,端点、字段、类型、长度全是后端定的
  • 后端加一个字段 → 契约自动变 → 前端重新生成时自动跟上

甚至可以更进一步——从 OpenAPI 直接生成 typed client 或 mock,把约束落到类型系统里。前后端联调的对错,在契约层面就被拦住了,而不是联调的时候才发现。

不管载体是 OpenAPI、GraphQL schema 还是 gRPC proto,本质一样:机器可读的契约,让确定性可以沿技术栈往下走。

拓展

Rules + Spec + Skills 三位一体

沉淀什么 管什么事 直接效果
Rules 约束类(命名规范、目录约定、禁止项、不可逆操作的审批规则) 产出贴着团队的工程口味,不需要每个 PR 手动纠风格
Spec 意图类(模块要解决什么、边界在哪、明确不做什么) AI 理解的是”为什么”,不是照抄上次的代码
Skills 可复用动作(固化的常用操作流程,如”给新数据表生成 CRUD + 测试”的步骤) 不用每次重新拼装步骤

Rules 模板(放进项目根,文件名常用 AGENTS.md

让 AI 不必每次从零理解你的项目。Rules 管”约束”,让产出符合工程口味。
纯文本、和框架无关——换任何 AI 工具都还在起作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 项目名 · Agent Rules

## 命名
- 文件 / 目录用 <你的约定,如 snake_case>
- 函数动词开头;布尔量用 is_/has_ 前缀

## 目录
- 业务逻辑放 <where>;测试放 <where>,文件名 `test_*.py`
- 不在 <where><what>

## 写代码的口味
- 先 spec、再测试、再实现;一个改动一个小提交
- 公共函数要有类型签名
- 错误要抛明确异常,不静默吞掉

## 禁止
- 不要引入未在 requirements 里的新依赖(先问)
- 不要为通过类型检查加无意义的抽象
- 不可逆操作(删数据/动钱/对外发布)必须留给人确认

## 测试
- 改了行为就更新/补测试;不许把失败测试注释掉
用法
  • 5~8 条起步,别一上来写满。每次 AI 产出跑偏一次,就回来补一条。
  • 配合 Spec(意图)和 Skills(可复用动作),三者一起就是”知识沉淀”。
  • 想看样板:很多开源项目根目录都有 AGENTS.md / CLAUDE.md,可借鉴格式。

写完以后,谁来担保它是对的

测试全绿不等于可以直接上线。中间还差一步——谁来担保这段代码放进生产环境不会出问题?

第一道关:AI 自我审查(自审)。 换一个会话或让 AI 换审查视角,把产出丢给它,让它在几个具体方向上找问题:边界有没有处理、异常路径上有没有静默吞错、和 spec 有没有偏差、哪些行为缺测试。它能抓到很多”明显错”——没处理的 None、漏掉的异常分支、被测漏的边界。

但它抓不住一类问题:代码完全正确,但理解错了需求。因为 AI 不知道真实的业务意图。

第二道关:人最终决策。 不可逆的操作(删数据、动钱、对外发布、改线上配置)、业务语义对不对——这些没有机器能替人判断,必须在合并之前由人确认。两关都放在合并前,而不是上线后补救。

整条链路:

两道审查关:AI 自审抓明显错,人决策抓和意图不符与不可逆操作,都在合并前

这套两道关是个人和小队尺度的做法——一段审查 prompt 加一份合并前 checklist 就能跑起来。


审查回路:AI 自审 prompt + 合并前 checklist

“测试绿”和”可以上线”中间,差一步谁来担保。两道关:机器能判的自动拦,机器判不了的人来批。

第一关:AI 自审 prompt(抓”明显错”)
1
2
3
4
5
6
你是严格的代码审查者。审查下面这段产出,只列问题、不夸奖。逐条检查:
1. 边界:空输入、极值、超长、并发、重复调用有没有处理?
2. 异常路径:失败时返回什么?有没有静默吞错?
3. 漏测:哪些行为没有对应测试?
4. 与 spec 的偏差:有没有做了 spec 没要求、或漏了 spec 要求的?
对每条给出:问题 + 它会在什么输入下出事 + 具体改法。

边界提醒:AI 自审能抓”明显错”,抓不住”代码对但理解错了需求”——因为它不知道真实意图。所以还要第二关。

第二关:合并前 checklist(人来批,抓”和意图不符 + 不可逆”)
  • 这段实现真的解决了 issue 要的那个问题吗?(不是另一个相近问题)
  • 有没有不可逆副作用(删数据 / 动钱 / 对外发布 / 改线上配置)?有 → 必须人确认
  • 业务语义对吗(机器判不了的那部分)?
  • 测试覆盖了关键路径和边界吗?故意改坏一处,测试会红吗?
  • 影响范围清楚吗(改这块会牵动谁)?
位置

把两关都放在合并前 / 上线前,让风险进主干之前被拦住。


最小可执行流程

如果只保留最小动作,我会把 SDD + TDD 压成这 9 步:

  1. 写一份十几行的 spec:目标、数据、接口、边界、明确不做。
  2. 人先审 spec:确认它描述的是正确问题,而不是看起来像正确问题。
  3. 让 AI 按 spec 只写测试,不写实现。
  4. 确认新增或变更行为的测试先红,而且红在预期原因上。
  5. 让 AI 补实现到全绿。
  6. 实现阶段默认不改测试;若必须改,先说明原测试哪里不符合 spec。
  7. 跑静态检查、全量测试和实际 example。
  8. 如果有前后端,把后端 OpenAPI / GraphQL schema / gRPC proto 当作前端 spec。
  9. 合并前做一次 AI 自审 + 人工 checklist,尤其确认业务语义和不可逆操作。

核心不是”让 AI 多写一点”,而是反过来:让 AI 在更明确的约束里写。spec 定义什么是对,测试把对变成机器能判,契约把对继续传给下游,人最后负责那些机器判不了的业务意图和生产风险。


SDD+TDD的探讨
https://www.omjmmd.xyz/2026/06/10/SDD-TDD的探讨/
作者
博主
发布于
2026年6月10日
许可协议