本文目录导读:

- 背景与角色设定
- 业务需求(原始描述)
- ATDD第一步:需求澄清(三方一起讨论)
- ATDD第二步:定义验收标准(行为驱动语言 Gherkin格式)
- ATDD第三步:将验收标准自动化
- ATDD第四步:运行测试 -> 全部失败(红灯)
- ATDD第五步:开发实现代码
- ATDD第六步:再次运行测试 -> 全部通过(绿灯)
- ATDD第七步:重构(保持测试通过)
- 扩展:当需求变更时
- 十一、ATDD vs TDD 对比
- ATDD的标准流程
- 十三、ATDD 工具链推荐
下面给你一个完整、可执行的ATDD(Acceptance Test-Driven Development,验收测试驱动开发)案例。
我会以 “用户登录功能” 为例,从业务需求到需求澄清,再到编写验收测试,最后到代码实现,完整走一遍ATDD流程。
背景与角色设定
| 角色 | 人物 | 关注点 |
|---|---|---|
| 业务方(PO) | 王总 | 业务规则、用户体验 |
| 开发 | 小李 | 代码实现、技术可行性 |
| 测试 | 小张 | 验收标准、测试用例 |
业务需求(原始描述)
王总说:“我们要做一个登录功能,用户输入用户名和密码就能登录,如果密码错了要提示错误,登录成功后要跳转到首页。”
ATDD第一步:需求澄清(三方一起讨论)
在写代码之前,三方坐在一起,把需求问清楚。
典型对话:
- 测试小张:密码错误提示什么?密码连续错几次要锁定吗?
- 业务王总:提示“用户名或密码错误”,不要告诉用户是哪个错了(防止黑客试探),密码错5次锁定账号10分钟。
- 开发小李:用户名是邮箱还是手机号?有没有验证码?
- 业务王总:目前支持用邮箱登录,验证码先不做,MVP阶段先不做。
ATDD第二步:定义验收标准(行为驱动语言 Gherkin格式)
经过澄清,测试小张把需求转化为可执行的验收标准(Gherkin格式 —— Given-When-Then):
Feature: 用户登录
Scenario: 正确凭据登录成功
Given 用户输入正确的用户名 "zhangsan@example.com"
And 用户输入正确的密码 "abc123"
When 用户点击登录按钮
Then 系统跳转到首页
And 系统显示登录成功提示
Scenario: 密码错误登录失败
Given 用户输入正确的用户名 "zhangsan@example.com"
And 用户输入错误的密码 "wrongpass"
When 用户点击登录按钮
Then 系统显示"用户名或密码错误"
And 系统停留在登录页
Scenario: 连续5次密码错误锁定账号
Given 用户输入正确的用户名 "zhangsan@example.com"
And 用户输入错误的密码 "wrongpass"
When 用户连续尝试登录5次失败
Then 系统提示"账号已被锁定,请10分钟后再试"
And 锁定期间即使输入正确密码也登录失败
Scenario: 用户名不存在
Given 用户输入不存在的用户名 "ghost@example.com"
And 用户输入任意密码 "abc123"
When 用户点击登录按钮
Then 系统显示"用户名或密码错误"
✅ 写好了这个文件之后,这就是团队和业务的“契约”,只有这个文件里的场景全部通过,功能才算完成。
ATDD第三步:将验收标准自动化
测试小张使用 Cucumber(BDD框架) 将这些Gherkin场景转化为自动化测试代码。
public class LoginSteps {
private LoginPage loginPage;
private String actualMessage;
@Given("用户输入正确的用户名 {string}")
public void enterCorrectUsername(String username) {
loginPage.enterUsername(username);
}
@Given("用户输入正确的密码 {string}")
public void enterCorrectPassword(String password) {
loginPage.enterPassword(password);
}
@When("用户点击登录按钮")
public void clickLoginButton() {
actualMessage = loginPage.clickLogin();
}
@Then("系统跳转到首页")
public void verifyRedirectToHome() {
assertEquals("homepage", loginPage.getCurrentPage());
}
@Then("系统显示{string}")
public void verifyMessage(String expectedMsg) {
assertEquals(expectedMsg, actualMessage);
}
// ... 其他步骤实现
}
ATDD第四步:运行测试 -> 全部失败(红灯)
代码还没有实现,或者只写了空壳类。
运行测试结果:
1) Scenario: 正确凭据登录成功 - FAILED
2) Scenario: 密码错误登录失败 - FAILED
3) Scenario: 连续5次密码错误锁定账号 - FAILED
4) Scenario: 用户名不存在 - FAILED
红灯是正常的,这符合 TDD/ATDD的“先失败后成功”原则。
ATDD第五步:开发实现代码
开发小李现在根据 验收测试 来写实现代码,目标是让测试全部变绿。
public class LoginService {
private static final int MAX_ATTEMPTS = 5;
private static final long LOCK_DURATION_MS = 10 * 60 * 1000; // 10分钟
private Map<String, Account> accounts = new HashMap<>();
private Map<String, LoginAttempt> attemptHistory = new HashMap<>();
public LoginResult login(String username, String password) {
// 1. 检查是否被锁定
LoginAttempt attempt = attemptHistory.getOrDefault(username, new LoginAttempt());
if (attempt.isLocked()) {
return new LoginResult(false, "账号已被锁定,请10分钟后再试");
}
// 2. 验证账号存在
Account account = accounts.get(username);
if (account == null) {
recordFailure(attempt, username);
return new LoginResult(false, "用户名或密码错误");
}
// 3. 验证密码
if (account.getPassword().equals(password)) {
resetAttempt(username);
return new LoginResult(true, "登录成功");
} else {
recordFailure(attempt, username);
if (attempt.getCount() >= MAX_ATTEMPTS) {
attempt.lock();
return new LoginResult(false, "账号已被锁定,请10分钟后再试");
}
return new LoginResult(false, "用户名或密码错误");
}
}
private void recordFailure(LoginAttempt attempt, String username) {
attempt.incrementCount();
attemptHistory.put(username, attempt);
}
private void resetAttempt(String username) {
attemptHistory.remove(username);
}
}
ATDD第六步:再次运行测试 -> 全部通过(绿灯)
1) Scenario: 正确凭据登录成功 - PASSED
2) Scenario: 密码错误登录失败 - PASSED
3) Scenario: 连续5次密码错误锁定账号 - PASSED
4) Scenario: 用户名不存在 - PASSED
✅ 绿了,功能就完成了!
ATDD第七步:重构(保持测试通过)
开发小李对代码进行重构:
- 将常量提取到配置文件
- 增加日志记录
- 优化代码结构
重构后再次运行测试,确保依然是绿灯。
扩展:当需求变更时
假设王总过来说:“密码连续错误5次,锁定时间改为30分钟。”
你不能直接改代码!
你需要这样做:
-
先改验收测试文件(Gherkin)
Given 用户输入正确的用户名 "zhangsan@example.com" And 用户输入错误的密码 "wrongpass" When 用户连续尝试登录5次失败 Then 系统提示"账号已被锁定,请30分钟后再试"
-
运行测试 -> 会有一个测试失败(因为代码还是10分钟)
-
修改实现代码,将锁定时间改为30分钟
-
再次运行测试 -> 全部通过
这就是ATDD的核心优势:验收测试先行,需求变更也由测试驱动。
十一、ATDD vs TDD 对比
| 维度 | TDD | ATDD |
|---|---|---|
| 测试对象 | 单元测试(函数/方法) | 验收测试(系统行为) |
| 参与者 | 开发 | 开发 + 测试 + 业务 |
| 粒度 | 代码级 | 业务/需求级 |
| 测试语言 | 代码(JUnit等) | 自然语言(Gherkin) |
| 目的 | 保证代码质量 | 保证需求被正确实现 |
ATDD的标准流程
① 三方讨论需求
↓
② 写出Gherkin验收标准
↓
③ 自动化验收测试
↓
④ 运行测试失败(红灯)
↓
⑤ 开发写实现代码
↓
⑥ 测试通过(绿灯)
↓
⑦ 重构
↓
⑧ 持续回归(需求变更时重复③-⑥)
十三、ATDD 工具链推荐
| 工具 | 用途 |
|---|---|
| Cucumber / SpecFlow | Gherkin语言解析、测试执行 |
| JUnit / NUnit | 底层断言 |
| Selenium / Playwright | UI自动化 |
| RestAssured | API自动化验收 |
| Jira(Xray) | 验收测试管理 |
如果你告诉我你的实际业务场景(比如订单、支付、注册、库存管理),我可以帮你写一套针对那个场景的完整ATDD案例(包括完整的Gherkin文件和测试代码模板)。