名称: tdd-workflow 描述: 具有全面覆盖要求的测试驱动开发工作流,包括单元、集成和端到端测试 许可证: MIT 复杂度: 中级 学习时间: 30分钟 先决条件:
- testing-patterns 标签:
- tdd
- test-driven-development
- red-green-refactor
- coverage
- quality 输入:
- 功能需求
- 源代码 输出:
- 测试套件
- 实现代码 副作用:
- 创建文件
- 运行命令 触发器:
- 用户询问关于TDD
- 用户询问关于测试
- 文件类型:*.test.ts
- 文件类型:*.spec.py 互补:
- testing-patterns
- verification-loop 层级: 核心 元数据: 来源: affaan-m/everything-claude-code 改编者: ai-skills 类别: 开发工作流
测试驱动开发工作流
强制执行测试驱动开发原则,实现所有层面的全面测试覆盖。
何时激活
- 编写新功能
- 修复bug或问题
- 重构现有代码
- 添加API端点
- 创建新组件
- 实现业务逻辑
核心原则
1. 测试先于代码
始终先写测试,然后实现代码使测试通过。没有例外。
2. 覆盖要求
- 最低80%覆盖(单元 + 集成 + E2E)
- 覆盖所有边界情况
- 测试错误场景
- 验证边界条件
- 快乐路径和悲伤路径
3. 测试金字塔
┌────────┐
│ E2E │ (10-20%)
├────────┤
│ 集成 │ (20-30%)
├────────┤
│ 单元 │ (50-70%)
└────────┘
TDD工作流:红-绿-重构
步骤1: 红 - 编写失败的测试
从用户故事开始:
作为[角色],我想要[操作],以便[好处]
示例:
作为用户,我想要按类别搜索产品,
以便我能快速找到相关商品。
编写测试:
describe('产品搜索', () => {
it('返回按类别过滤的产品', async () => {
const products = await searchProducts({ category: 'electronics' });
expect(products).toHaveLength(5);
expect(products.every(p => p.category === 'electronics')).toBe(true);
});
});
步骤2: 运行测试(它们应该失败)
npm test
# ✗ 产品搜索 > 返回按类别过滤的产品
# TypeError: searchProducts 不是一个函数
这是好的! 红阶段确认测试在工作。
步骤3: 绿 - 实现最小代码
编写刚好足够通过的代码:
export async function searchProducts(filters: SearchFilters) {
const { category } = filters;
return db.products.findMany({
where: { category }
});
}
步骤4: 运行测试(它们应该通过)
npm test
# ✓ 产品搜索 > 返回按类别过滤的产品 (42ms)
步骤5: 重构 - 改进代码
现在重构,同时保持测试通过:
export async function searchProducts(filters: SearchFilters) {
const query = buildSearchQuery(filters);
const results = await executeSearch(query);
return transformResults(results);
}
再次运行测试以确保它们仍然通过。
测试类型
单元测试
目的:在隔离中测试单个函数
describe('calculateDiscount', () => {
it('为基本层级应用10%折扣', () => {
const price = calculateDiscount(100, 'basic');
expect(price).toBe(90);
});
it('为高级层级应用20%折扣', () => {
const price = calculateDiscount(100, 'premium');
expect(price).toBe(80);
});
it('为无效层级抛出错误', () => {
expect(() => calculateDiscount(100, 'invalid')).toThrow();
});
});
集成测试
目的:测试多个组件协同工作
describe('用户注册API', () => {
it('创建用户并发送欢迎邮件', async () => {
const response = await request(app)
.post('/api/register')
.send({ email: 'test@example.com', password: 'secret' }); // allow-secret
expect(response.status).toBe(201);
expect(response.body.user.email).toBe('test@example.com');
// 验证邮件已发送
const sentEmails = await testEmailService.getSentEmails();
expect(sentEmails).toHaveLength(1);
expect(sentEmails[0].to).toBe('test@example.com');
});
});
端到端测试(Playwright/Cypress)
目的:通过UI测试完整的用户流程
test('用户可以完成结账流程', async ({ page }) => {
await page.goto('/products');
await page.click('[data-testid="add-to-cart"]');
await page.click('[data-testid="cart"]');
await page.click('[data-testid="checkout"]');
await page.fill('[name="cardNumber"]', '4242424242424242');
await page.click('[data-testid="complete-order"]');
await expect(page.locator('.success-message')).toBeVisible();
await expect(page).toHaveURL(/\/order-confirmation/);
});
覆盖验证
实现后,检查覆盖:
npm test -- --coverage
# 输出:
# 文件 | % 语句 | % 分支 | % 函数 | % 行
# --------------|---------|----------|---------|--------
# 所有文件 | 84.2 | 78.5 | 91.3 | 85.1
要求:
- 语句: > 80%
- 分支: > 75%
- 函数: > 85%
- 行: > 80%
边界情况清单
始终测试:
- [ ] 空输入
- [ ] 空值/未定义值
- [ ] 大数据集
- [ ] 并发操作
- [ ] 网络故障
- [ ] 数据库错误
- [ ] 无效数据类型
- [ ] 边界值(0, -1, MAX_INT)
- [ ] 未授权访问
- [ ] 速率限制
模拟策略
何时模拟
- 外部API
- 数据库调用(在单元测试中)
- 时间依赖函数
- 文件系统操作
- 第三方服务
示例模拟
// 模拟外部服务
vi.mock('./emailService', () => ({
sendEmail: vi.fn().mockResolvedValue({ success: true })
}));
// 模拟数据库
vi.mock('./db', () => ({
users: {
findUnique: vi.fn().mockResolvedValue({ id: 1, name: '测试' })
}
}));
// 模拟 Date.now()
vi.spyOn(Date, 'now').mockReturnValue(1234567890000);
测试组织
src/
特性/
产品/
product.service.ts
product.service.test.ts # 单元测试
product.integration.test.ts # 集成测试
product.e2e.test.ts # E2E测试
调试失败的测试
# 运行单个测试文件
npm test -- product.service.test.ts
# 运行单个测试
npm test -- -t "正确计算折扣"
# 在监视模式下运行
npm test -- --watch
# 运行带覆盖
npm test -- --coverage --no-cache
集成点
互补:
- verification-loop:用于预提交验证
- testing-patterns:用于测试设计模式
- deployment-cicd:用于CI测试执行
- security-implementation-guide:用于安全测试
工作流总结
- 红:编写失败的测试
- 绿:实现最小代码
- 重构:改进同时保持测试通过
- 验证:检查覆盖是否满足要求
- 提交:仅在所有测试通过后提交
永不跳过步骤。永不提交未测试的代码。
相关技能
互补技能(一起使用)
- testing-patterns - 全面的测试模式,用于单元、集成和E2E测试
- verification-loop - 预提交验证工作流,验证所有质量门限
- deployment-cicd - CI管道设置,用于自动化测试执行
替代技能(相似目的)
- 无 - TDD是特定方法论,补充其他测试方法
先决技能(先学习)
- testing-patterns - 理解测试类型和框架有助于TDD