BDD 行为驱动开发:用自然语言描述需求
BDD 行为驱动开发:用自然语言描述需求
什么是行为驱动开发
行为驱动开发(Behavior-Driven Development,BDD)是一种敏捷软件开发实践,它将业务需求、开发设计与测试验证统一为“可执行的自然语言规范”。BDD 强调通过具体示例来描述系统行为,让技术团队和非技术干系人(产品经理、QA、业务分析师)使用同一份文档沟通,减少歧义,确保从需求到代码的可追溯性。
BDD 的核心并非测试框架,而是一种协作方法:先通过示例澄清需求,再将这些示例转化为自动化验收标准,最后驱动实现。它建立在测试驱动开发(TDD)与领域驱动设计(DDD)的理论基础之上,用“用户故事 + 场景”的结构让所有人都能看懂。
BDD 的核心要素
通用语言:Gherkin
BDD 采用结构化自然语言描述行为,最常用的语法是 Gherkin。它是一种基于行、使用关键字编写的格式,支持多种人类语言(中文、英文、日文等)。Gherkin 的核心关键字包括:
- Feature(功能):对软件功能的简要描述,通常对应一个用户故事。
- Scenario(场景):具体的业务规则示例,描述在特定条件下系统应如何表现。
- Given(假设):场景的前置条件或上下文。
- When(当):用户执行的操作或触发的事件。
- Then(那么):预期的结果或系统应输出的信息。
- And / But(并且 / 但是):用于连接多个 Given / When / Then 步骤。
3 个关键角色(3 Amigos)
BDD 依赖三类角色的紧密协作:
- 业务人员(Business):定义需求、提供示例,确保场景覆盖业务规则。
- 开发人员(Development):将场景转化为可执行的测试和实现代码。
- 测试人员(Testing):负责场景的质量,补充边界案例和异常流程。
他们定期举行“示例映射”会议,用具体示例澄清模糊需求,形成 Gherkin 场景。
可执行规约(Living Documentation)
Gherkin 文件不是静态文档,而是活的文档。它通过自动化工具(Cucumber、SpecFlow、Behat 等)与测试框架绑定,执行后直接生成测试报告。当场景通过时,文档即为最新的系统行为说明;失败时则标记出与实现不一致的地方,提示需要修复代码或调整需求。
BDD 工作流程
BDD 遵循一个简单而强大的“由外向内”开发循环:
-
发现(Discovery)
产品负责人与团队一起编写用户故事,并用具体示例讨论可能的场景。强调“用示例说话”,避免抽象描述。 -
制定(Formulation)
将示例转化为 Gherkin 格式的 Feature 文件。确保每个场景都有一个清晰的标题和完整的 Given-When-Then 结构。 -
自动化(Automation)
开发人员基于 Gherkin 场景编写 Step Definitions(步骤定义),将自然语言映射到具体的测试代码或自动化操作方法。此时测试是失败的(红)。 -
实现(Implementation)
编写应用代码让场景通过。遵循 TDD 的微观循环:写一点实现,运行场景,逐渐使所有步骤变绿。 -
重构与维护
代码通过后重构优化,并确保场景仍然通过。随着需求变化,团队同步更新 Gherkin 文件和步骤定义,保持文档、测试和代码始终一致。
flowchart LR
A[发现示例] --> B[编写Gherkin场景]
B --> C[实现步骤定义]
C --> D[场景失败(红)]
D --> E[编写应用代码]
E --> F[场景通过(绿)]
F --> G[重构代码]
G --> D
动手实践:一个简单的 BDD 示例
我们以实现一个“用户登录”功能为例,逐步展示 BDD 的完整过程。
1. 编写 Feature 文件
文件 login.feature 使用中文描述:
# language: zh-CN
功能: 用户登录
作为注册用户
我想要使用邮箱和密码登录
以便访问我的个人中心
场景: 使用有效凭据成功登录
假设 我未登录状态
当 我使用邮箱 "[email protected]" 和密码 "correct123" 进行登录
那么 我应被重定向到个人中心页面
而且 页面应显示 "欢迎回来,Alice"
场景: 使用错误密码登录失败
假设 我未登录状态
当 我使用邮箱 "[email protected]" 和密码 "wrongpass" 进行登录
那么 我应停留在登录页面
而且 页面应显示错误提示 "邮箱或密码不正确"
2. 自动化步骤定义
用 Cucumber.js(JavaScript 版)为例编写步骤定义:
const { Given, When, Then } = require('@cucumber/cucumber');
const assert = require('assert');
let currentPage, loginPage, userCredentials;
Given('我未登录状态', function () {
loginPage = new LoginPage();
loginPage.visit();
});
When('我使用邮箱 {string} 和密码 {string} 进行登录', function (email, password) {
userCredentials = { email, password };
currentPage = loginPage.submitLogin(email, password);
});
Then('我应被重定向到个人中心页面', function () {
assert.strictEqual(currentPage.url(), '/profile');
});
Then('页面应显示 {string}', function (expectedMessage) {
const actualMessage = currentPage.getWelcomeMessage();
assert.strictEqual(actualMessage, expectedMessage);
});
Then('我应停留在登录页面', function () {
assert.strictEqual(currentPage.url(), '/login');
});
3. 运行并观察结果
第一次运行测试,所有步骤定义未实现时报告为“未定义”或失败。随着逐步编写 LoginPage 类和对应的路由逻辑,场景将从失败转为通过。最终所有场景变绿,意味登录功能符合需求。
BDD 与 TDD 的关系
| 维度 | TDD(测试驱动开发) | BDD(行为驱动开发) |
|---|---|---|
| 关注点 | 代码内部设计,单元测试 | 系统外部行为,端到端场景 |
| 语言 | 技术语言(assert, expect) | 自然语言,业务可读 |
| 编写主体 | 开发人员 | 业务、开发、测试共同编写 |
| 文档价值 | 低,测试即文档(但对非技术人员不友好) | 高,Gherkin 文件即活的文档 |
| 典型框架 | JUnit, NUnit, PHPUnit | Cucumber, SpecFlow, Behat, JBehave |
BDD 并不替代 TDD,而是建立在 TDD 之上。TDD 驱动内部设计,BDD 驱动外部行为。在实现步骤定义时,开发人员仍可继续使用 TDD 编写单元测试。
BDD 的典型应用场景与优势
适用场景
- 需求变更频繁、需要持续与业务对齐的项目。
- 跨职能团队,非技术人员需要参与验收标准制定。
- 微服务或 API 开发,BDD 可清晰描述接口合约。
- 遗留系统改造,可先为现有行为补充 BDD 文档再安全重构。
核心优势
- 消除歧义:用具体示例取代抽象描述,所有角色对需求理解一致。
- 活文档:文档与测试执行同步更新,无文档过期问题。
- 快速反馈:场景失败立即指出哪个业务规则被破坏。
- 回归保护网:积累的场景成为可靠的自动化验收套件。
- 促进协作:3 Amigos 会议让团队对需求有共同所有权。
常见误区与最佳实践
误区1:把 Gherkin 当作测试脚本编写工具
BDD 场景应该描述业务意图,而不是 UI 操作细节。避免“点击 ID 为 xxx 的按钮”这类脆弱的步骤,应表达为“提交登录表单”。保持场景技术无关。
误区2:场景过于冗长或包含多个 Given-When-Then
每个场景应当只验证一条行为规则。如果一个场景包含很多分支或循环,应拆分为独立的场景或用场景大纲(Scenario Outline)进行参数化。
误区3:忽略非功能性场景
认证、授权、性能、安全等非功能需求也可以用场景描述,例如:“当用户在 3 秒内未收到响应,那么系统应显示超时提示”。
最佳实践总结:
- 使用业务语言,避免实现细节。
- 场景标题使用“主语 + 动词”的形式,读者一眼看出验证的内容。
- 异步操作或后台任务,增加显式等待步骤(
当 我等待订单状态变为“已确认”)。 - 定期举行“示例映射”会议,保持 Feature 文件整洁并贴合当下需求。
- 将 Gherkin 文件放在版本控制系统中,与实践代码一同提交。
工具生态速览
| 工具 | 语言/平台 | 特点 |
|---|---|---|
| Cucumber | Ruby, JVM, JS, C++ 等 | 最流行的 BDD 框架,多语言支持 |
| SpecFlow | .NET | 与 Visual Studio 深度集成,支持 Azure DevOps |
| Behat | PHP | 与 Symfony 等框架结合紧密 |
| JBehave | Java | 纯 Java 实现,Gherkin 语法稍早版本 |
| behave | Python | 轻量级,适合 Python 生态 |
| Serenity BDD | Java | 提供丰富的活文档和测试聚合报告 |
选择工具时优先考虑团队技术栈,确保所有角色都能方便参与 Gherkin 文件的阅读与编写。
结语
行为驱动开发不仅是一种技术实践,更是一种协作文化。它通过将需求转化为可执行的自然语言,让软件交付团队能够持续聚焦于业务价值,而不是技术实现细节。从今天起,下次拿到用户故事时,不妨邀请业务伙伴一起写下第一个 Gherkin 场景——你会看到不同的改变。