TDD测试驱动开发
红-绿-重构循环
// 第一步(红):编写失败的测试
// 测试尚未存在的功能
test('FizzBuzz 对 3 的倍数返回 "Fizz"', () => {
expect(fizzBuzz(3)).toBe('Fizz'); // ❌ fizzBuzz 未定义
expect(fizzBuzz(6)).toBe('Fizz');
});
// 第二步(绿):编写最少代码使测试通过
function fizzBuzz(n) {
if (n % 3 === 0) return 'Fizz'; // ✅ 只写必要的代码
return String(n);
}
// 第三步(重构):在不破坏测试的前提下改善代码
// 继续添加测试,然后完善实现:
test('FizzBuzz 对 5 的倍数返回 "Buzz"', () => {
expect(fizzBuzz(5)).toBe('Buzz');
});
test('FizzBuzz 对 15 的倍数返回 "FizzBuzz"', () => {
expect(fizzBuzz(15)).toBe('FizzBuzz');
});
// 最终实现:
function fizzBuzz(n) {
if (n % 15 === 0) return 'FizzBuzz';
if (n % 3 === 0) return 'Fizz';
if (n % 5 === 0) return 'Buzz';
return String(n);
}
TDD 方法对比
| 方法 | 方向 | 从何开始 | 适合场景 |
|---|---|---|---|
| 由内而外(Detroit/Chicago) | 单元优先,后集成 | 领域对象、纯逻辑 | 熟悉的领域、算法实现 |
| 由外而内(London/Mockist) | 验收测试优先,Mock 协作者 | 高层验收测试 | API 驱动设计、内部细节待定 |
| BDD(行为驱动) | 用户场景优先 | Gherkin feature 文件 | 跨团队沟通、验收标准 |
BDD 与 Gherkin
# Feature 文件(Gherkin 语法)
功能: 用户登录
作为一个注册用户
我希望能登录账户
以便访问我的仪表板
场景: 登录成功
假如 我在登录页面
当 我输入邮箱 "[email protected]" 和密码 "correct123"
并且 我点击"登录"按钮
那么 我应该被重定向到仪表板
并且 我应该看到"欢迎回来"
场景: 密码错误登录失败
假如 我在登录页面
当 我输入邮箱 "[email protected]" 和密码 "wrong"
并且 我点击"登录"按钮
那么 我应该看到"邮箱或密码不正确"
并且 我仍然停留在登录页面
# 步骤定义(JavaScript — Cucumber.js)
Given('我在登录页面', async () => {
await page.goto('/login');
});
When('我输入邮箱 {string} 和密码 {string}', async (email, password) => {
await page.fill('#email', email);
await page.fill('#password', password);
});