BDD 的旧船与新船员

什么是 BDD?

BDD = Behavior-Driven Development,2006 年由 Dan North 在 ThoughtWorks 提出。

它的核心想法只有一句话:

把测试用例写成业务方能读懂的结构化自然语言,业务、开发、测试三方围绕这份文档协作,文档同时就是可执行的测试。

它的旗舰工具是 Cucumber(2008 年由挪威工程师 Aslak Hellesøy 用 Ruby 写出来,后来扩散到 Java / JavaScript / Python / .NET 等所有主流语言)。Cucumber 用的 DSL 叫 Gherkin

Feature: 用户登录

  Scenario: 密码正确,登录成功
    Given 用户 alice 已注册
    When alice 用正确密码登录
    Then 应返回 200
    And 响应里应包含 access_token

  Scenario: 密码错误,登录失败
    Given 用户 alice 已注册
    When alice 用错误密码登录
    Then 应返回 401
    And 响应里应包含 "invalid_credentials"

开发再用代码"实现"每一句(叫 step definition),跑 cucumber 命令 → 它解析 .feature → 调对应 step → 输出通过/失败。业务能读、开发能跑、测试能用,spec / test / doc 三位一体

听上去很美,为什么没火?

BDD 2010-2015 是高峰,之后慢慢边缘化。原因在五个深层困难:

1. 写 Gherkin 是开发的负担

理论上业务方写。实际上业务方不写,最后还是开发自己编。编完业务方也不读——业务方觉得"这是测试代码我看不懂",跳过去开下一个会。变成开发写给开发自己看的双份测试,还不如直接写单元测试

2. Step definitions 工作量翻倍

每条 Gherkin 句子都要写对应的实现代码:

@given('用户 {name} 已注册')
def step_user_registered(context, name): ...

@when('{name} 用正确密码登录')
def step_login_correct(context, name): ...

业务改一句话 → 改 .feature → 改 step def → 可能还要改实现。三处同步,没人愿意干。

3. 维护成本与收益不对等

写出来的 .feature 文件在两年后变成"考古遗迹",业务规则演化了 5 次,scenario 还停在最初的版本。于是开发干脆注释掉 cucumber 测试只跑 unit test,BDD 名存实亡。

4. Three Amigos 在大公司开不动

BDD 最重要的实践是"业务 + 开发 + 测试三方在写代码前坐到一起"。听上去完美,实操中推不动

  • 业务方在外面跑客户,没时间
  • 测试编制紧,一个 QA 顶五个项目
  • 开发要赶迭代,会议越少越好

会议开不起来,BDD 就退化成"开发自己写双份测试"。


BDD 提出的所有理念都对,但它依赖一种纪律:业务方愿意写、开发方愿意维护双份代码、三方愿意为每个 user story 开同步会。这种纪律在创业公司没建立、在大公司被绩效压力磨没了。好主意没有合格的执行者

穿着 SDD 外衣的 BDD 实践

BDD 最有价值的 6 条实践在 AI 这个永不疲倦的合作者支持下,被 SDD 继承了

1. 例子优于规则

BDD 原话:“examples are the best specification”。

SDD 怎么用:

  • GitHub Spec Kit / OpenSpec 的 spec.md 都是 Requirement + Scenarios 结构。但 SDD 加了关键一步:AI 主动追问
  • BDD 时代要业务方主动想边界条件(很难)。SDD 时代 PM 写一条 happy path,AI 立刻问"那如果 X 呢、Y 呢、Z 呢",例子被对话引导出来。

2. Ubiquitous Language(统一术语)

BDD 原话:业务说的"客户"、测试写的 User、代码里的 account,必须是同一个东西。否则三方永远对不齐。

SDD 怎么用:

  • 放进项目目录的 CLAUDE.md / project.md / AGENTS.md / spec.md——AI 根据术语表对齐编码和测试。

3. Three Amigos 业产研协同开发

BDD 原话:业务 + 开发 + 测试三方写代码前坐到一起聊。

SDD 怎么用:

  • PM + Dev + AI。AI 是永远在场的第三方——没班要上、没别的会要开、能记住所有上下文。
  • PM 说"这里要这么做",AI 立刻追问"那如果输入是 X 呢"——这就是 Three Amigos 里 QA 本该问的问题。第三个 amigo 被 AI 补位

4. Given / When / Then 的思维结构

BDD 原话:每条 scenario 用 Given(前提)/ When(触发)/ Then(结果)三段拆解。它最大的价值不是当语法,是当思维模板——写不出 G/W/T 的需求,几乎肯定是没想清楚的需求。

SDD 怎么用:

  • OpenSpec 用 WHEN/THEN,Spec Kit 直接和 BDD 一样用 Gherkin,Kiro 用 EARS 格式。形式不同,骨架一样。
  • AI 在生成 spec 时也按这个模板要求自己,不会接受"系统应该正确处理"这种空话,会逼着 PM 把 Then 写具体。

5. Living Documentation(活文档)

BDD 原话.feature 不是文档 + 测试,是同一份东西。改业务 → 改 .feature → 测试失败 → 提醒你改代码。

SDD 怎么用:

  • 漂移问题 AI 接管。spec 改了,AI 改对应实现代码;代码改了 spec 没改,AI 在 PR review 里指出"实现和 spec 不一致"。
  • spec 有了主动维护者,一个不会嫌烦、不会忘记、不会请假的维护者。

6. 规范只定义做什么

BDD 原话:spec 描述"做什么",不描述"怎么做"。

写"调用 PostgreSQL 的 UPDATE…"—— 实现细节

写"账户余额应反映最新入账"—— 行为

SDD 怎么用:

  • 推到极致,spec 是 truth,code 是 derivative
  • 理想情况下,同一份 spec 可以让 AI 在 Python + PostgreSQL、Go + MySQL、Rust + SQLite 上各实现一遍。spec 不变,技术栈可以换。

一个例子

把 6 条全部用上,余额对账的"网银失败"场景可以写成:

Scenario: 网银接口全天拉取失败
  Given 账户 A 启用了余额对账
  And 当日所有重试均失败
  When T+100:30 对账任务运行
  Then 应生成一条 "余额缺失" 类型的差异
  And 差异状态为 "待核查"
  And 应向账户 A 的值班 IM 推送告警

这一个 scenario 同时是:

  • PM 能读懂的需求
  • QA 能跑的 acceptance test
  • AI 能生成代码的 spec
  • 上线后的活文档

“四位一体"这个 BDD 当年的理想,SDD 让它真正落地。

结尾

BDD 当年提出的实践都对,只是少了一个**永远耐心、永远不会"今天有别的事先不弄”**的合作者。

BDD 的旧船没有沉,它改名成了 SDD,并且等到了合格的船员,名字叫 Claude Code / Codex。