测试驱动开发工作流 tdd-workflow

测试驱动开发工作流是一种软件开发技能,用于通过先写测试再写代码的方式确保软件质量,强调高覆盖率、单元测试、集成测试和端到端测试,遵循红绿重构原则。关键词:TDD、测试驱动开发、覆盖率、单元测试、集成测试、E2E测试、红绿重构。

测试 0 次安装 0 次浏览 更新于 3/7/2026

name: 测试驱动开发工作流 description: 测试驱动开发工作流,包含全面的覆盖率要求,包括单元测试、集成测试和端到端测试 license: MIT complexity: 中级 time_to_learn: 30分钟 prerequisites:

  • 测试模式 tags:
  • tdd
  • 测试驱动开发
  • 红绿重构
  • 覆盖率
  • 质量 inputs:
  • 功能需求
  • 源代码 outputs:
  • 测试套件
  • 实现代码 side_effects:
  • 创建文件
  • 运行命令 triggers:
  • 用户询问关于tdd
  • 用户询问关于测试
  • 文件类型:*.test.ts
  • 文件类型:*.spec.py complements:
  • 测试模式
  • 验证循环 tier: 核心 metadata: source: affaan-m/everything-claude-code adapted-by: ai-skills category: 开发工作流

测试驱动开发工作流

通过全面的测试覆盖所有层来强制执行测试驱动开发原则。

何时激活

  • 编写新功能或功能
  • 修复错误或问题
  • 重构现有代码
  • 添加API端点
  • 创建新组件
  • 实现业务逻辑

核心原则

1. 测试先于代码

始终 先写测试,然后实现代码使测试通过。没有例外。

2. 覆盖率要求

  • 最低80%覆盖率(单元+集成+E2E)
  • 覆盖所有边缘情况
  • 测试错误场景
  • 验证边界条件
  • 快乐路径和悲伤路径

3. 测试金字塔

        ┌────────┐
        │   E2E  │  (10-20%)
        ├────────┤
        │  集成  │  (20-30%)
        ├────────┤
        │  单元  │  (50-70%)
        └────────┘

TDD工作流:红绿重构

步骤1: 红 - 编写失败的测试

从用户故事开始:

作为[角色],我想要[动作],以便[好处]

示例:
作为用户,我想要按类别搜索产品,
以便我能快速找到相关项目。

编写测试:

describe('ProductSearch', () => {
  it('按类别返回过滤后的产品', async () => {
    const products = await searchProducts({ category: 'electronics' });
    
    expect(products).toHaveLength(5);
    expect(products.every(p => p.category === 'electronics')).toBe(true);
  });
});

步骤2: 运行测试(它们应该失败)

npm test
# ✗ ProductSearch > 按类别返回过滤后的产品
#   TypeError: searchProducts 不是一个函数

这很好! 红阶段确认测试正在工作。

步骤3: 绿 - 实现最小代码

编写足够代码以通过:

export async function searchProducts(filters: SearchFilters) {
  const { category } = filters;
  return db.products.findMany({
    where: { category }
  });
}

步骤4: 运行测试(它们应该通过)

npm test
# ✓ ProductSearch > 按类别返回过滤后的产品 (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');
  });
});

E2E测试(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: 'Test' })
  }
}));

// 模拟 Date.now()
vi.spyOn(Date, 'now').mockReturnValue(1234567890000);

测试组织

src/
  features/
    products/
      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

集成点

互补:

  • 验证循环:用于提交前验证
  • 测试模式:用于测试设计模式
  • 部署CICD:用于CI测试执行
  • 安全实施指南:用于安全测试

工作流摘要

  1. :编写失败的测试
  2. 绿:实现最小代码
  3. 重构:改进同时保持测试通过
  4. 验证:检查覆盖率满足要求
  5. 提交:仅当所有测试通过时提交

切勿跳过步骤。切勿提交未测试的代码。


相关技能

互补技能(一起使用)

  • 测试模式 - 单元、集成和E2E测试的全面测试模式
  • 验证循环 - 提交前验证工作流,验证所有质量门
  • 部署CICD - 自动化测试执行的CI管道设置

替代技能(类似目的)

  • 无 - TDD是一种补充其他测试方法的特定方法学

先决技能(先学习)