testing-strategy.md 19 KB

测试策略

版本信息

版本 日期 描述 作者
2.10 2025-12-12 添加使用共享测试工具处理复杂组件的规范 James (Claude Code)
2.9 2025-12-12 添加测试用例编写规范,基于订单管理集成测试经验 James (Claude Code)
2.8 2025-11-11 更新包测试结构,添加模块化包测试策略 Winston
2.7 2025-11-09 更新为monorepo测试架构,清理重复测试文件 James
2.6 2025-10-15 完成遗留测试文件迁移到统一的tests目录结构 Winston
2.5 2025-10-14 更新测试文件位置到统一的tests目录结构 Claude
2.4 2025-09-20 更新测试策略与主架构文档版本一致 Winston

概述

本文档定义了D8D Starter项目的完整测试策略,基于monorepo架构和现有的测试基础设施。测试策略遵循测试金字塔模型,确保代码质量、功能稳定性和系统可靠性。

测试架构更新 (v2.8)

项目已重构为模块化包架构,测试架构相应调整为:

  • 基础设施包: shared-types、shared-utils、shared-crud、shared-test-util
  • 业务模块包: user-module、auth-module、file-module、geo-areas
  • 应用层: server (重构后),包含模块集成测试
  • web: Web应用,包含组件测试、集成测试和E2E测试
  • CI/CD: 独立的工作流分别处理各包的测试

包测试架构 (v2.8)

项目采用分层测试架构,每个包独立测试:

  • 基础设施包: 纯单元测试,不依赖外部服务
  • 业务模块包: 单元测试 + 集成测试,验证模块功能
  • 应用层: 集成测试,验证模块间协作
  • 共享测试工具: shared-test-util 提供统一的测试基础设施

测试金字塔策略

单元测试 (Unit Tests)

  • 范围: 单个函数、类或组件
  • 目标: 验证独立单元的correctness
  • 位置:
    • 基础设施包: packages/shared-*/tests/unit/**/*.test.ts
    • 业务模块包: packages/*-module/tests/unit/**/*.test.ts
    • server包: packages/server/tests/unit/**/*.test.ts
    • web应用: web/tests/unit/**/*.test.{ts,tsx}
  • 框架: Vitest
  • 覆盖率目标: ≥ 80%
  • 执行频率: 每次代码变更

集成测试 (Integration Tests)

  • 范围: 多个组件/服务协作
  • 目标: 验证模块间集成和交互
  • 位置:
    • 业务模块包: packages/*-module/tests/integration/**/*.test.ts
    • server包: packages/server/tests/integration/**/*.test.ts (模块集成测试)
    • web应用: web/tests/integration/**/*.test.{ts,tsx}
  • 框架: Vitest + Testing Library + hono/testing + shared-test-util
  • 覆盖率目标: ≥ 60%
  • 执行频率: 每次API变更

E2E测试 (End-to-End Tests)

  • 范围: 完整用户流程
  • 目标: 验证端到端业务流程
  • 位置: web/tests/e2e/**/*.test.{ts,tsx}
  • 框架: Playwright
  • 覆盖率目标: 关键用户流程100%
  • 执行频率: 每日或每次重大变更

测试环境配置

开发环境

// vitest.config.ts - 开发环境配置
export default defineConfig({
  test: {
    projects: [
      // Node.js 环境项目 - 后端测试
      {
        test: {
          include: [
            'tests/unit/server/**/*.test.{ts,js}',
            'tests/integration/server/**/*.test.{ts,js}'
          ],
          // ... 其他配置
        }
      },
      // Happy DOM 环境项目 - 前端组件测试
      {
        test: {
          include: [
            'tests/unit/client/**/*.test.{ts,js,tsx,jsx}',
            'tests/integration/client/**/*.test.{ts,js,tsx,jsx}'
          ],
          // ... 其他配置
        }
      }
    ]
  }
});

CI/CD环境

# GitHub Actions 测试配置 (模块化包架构)
name: Test Pipeline

jobs:
  # 基础设施包测试
  shared-packages-tests:
    runs-on: ubuntu-latest
    steps:
      - run: cd packages/shared-types && pnpm test
      - run: cd packages/shared-utils && pnpm test
      - run: cd packages/shared-crud && pnpm test
      - run: cd packages/shared-test-util && pnpm test

  # 业务模块包测试
  business-modules-tests:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_PASSWORD: test_password
          POSTGRES_DB: test_d8dai
    steps:
      - run: cd packages/user-module && pnpm test
      - run: cd packages/auth-module && pnpm test
      - run: cd packages/file-module && pnpm test
      - run: cd packages/geo-areas && pnpm test

  # 服务器集成测试
  server-integration-tests:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_PASSWORD: test_password
          POSTGRES_DB: test_d8dai
    steps:
      - run: cd packages/server && pnpm test

  # Web应用测试
  web-integration-tests:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_PASSWORD: test_password
          POSTGRES_DB: test_d8dai
    steps:
      - run: cd web && pnpm test:integration

  web-component-tests:
    runs-on: ubuntu-latest
    steps:
      - run: cd web && pnpm test:components

  web-e2e-tests:
    runs-on: ubuntu-latest
    steps:
      - run: cd web && pnpm test:e2e:chromium

测试覆盖率标准

各层覆盖率要求

测试类型 最低要求 目标要求 关键模块要求
单元测试 70% 80% 90%
集成测试 50% 60% 70%
E2E测试 关键流程100% 主要流程80% -

关键模块定义

  • 认证授权模块: 必须达到90%单元测试覆盖率
  • 数据库操作模块: 必须达到85%单元测试覆盖率
  • 核心业务逻辑: 必须达到80%集成测试覆盖率
  • 用户管理功能: 必须100% E2E测试覆盖

测试数据管理

测试数据策略

// 测试数据工厂模式
export function createTestUser(overrides = {}): User {
  return {
    id: 1,
    username: 'testuser',
    email: 'test@example.com',
    createdAt: new Date(),
    ...overrides
  };
}

// 使用示例
const adminUser = createTestUser({ role: 'admin' });
const inactiveUser = createTestUser({ active: false });

数据库测试策略

  • 单元测试: 使用内存数据库或完全mock
  • 集成测试: 使用专用测试数据库,事务回滚
  • E2E测试: 使用接近生产环境的数据库

数据清理策略

  1. 事务回滚 (推荐)
  2. 数据库清理 (每个测试后)
  3. 测试数据隔离 (使用唯一标识符)

测试执行流程

本地开发测试

基础设施包

# 运行所有基础设施包测试
cd packages/shared-types && pnpm test
cd packages/shared-utils && pnpm test
cd packages/shared-crud && pnpm test
cd packages/shared-test-util && pnpm test

# 生成覆盖率报告
cd packages/shared-utils && pnpm test:coverage

业务模块包

# 运行所有业务模块包测试
cd packages/user-module && pnpm test
cd packages/auth-module && pnpm test
cd packages/file-module && pnpm test
cd packages/geo-areas && pnpm test

# 运行单元测试
cd packages/user-module && pnpm test:unit

# 运行集成测试
cd packages/auth-module && pnpm test:integration

# 生成覆盖率报告
cd packages/user-module && pnpm test:coverage

server包

# 运行所有测试
cd packages/server && pnpm test

# 运行集成测试
cd packages/server && pnpm test:integration

# 生成覆盖率报告
cd packages/server && pnpm test:coverage

web应用

# 运行所有测试
cd web && pnpm test

# 运行单元测试
cd web && pnpm test:unit

# 运行集成测试
cd web && pnpm test:integration

# 运行组件测试
cd web && pnpm test:components

# 运行E2E测试
cd web && pnpm test:e2e:chromium

# 生成覆盖率报告
cd web && pnpm test:coverage

CI/CD流水线测试

  1. 代码推送 → 触发测试流水线
  2. 单元测试 → 快速反馈,必须通过
  3. 集成测试 → 验证模块集成,必须通过
  4. E2E测试 → 验证完整流程,建议通过
  5. 覆盖率检查 → 满足最低要求
  6. 测试报告 → 生成详细报告

质量门禁

测试通过标准

  • ✅ 所有单元测试通过
  • ✅ 所有集成测试通过
  • ✅ 关键E2E测试通过
  • ✅ 覆盖率满足最低要求
  • ✅ 无性能回归
  • ✅ 安全测试通过

失败处理流程

  1. 测试失败 → 立即通知开发团队
  2. 分析根本原因 → 确定是测试问题还是代码问题
  3. 优先修复 → 阻塞性问题必须立即修复
  4. 重新测试 → 修复后重新运行测试
  5. 文档更新 → 更新测试策略和案例

安全测试策略

安全测试要求

  • 输入验证测试: 所有API端点必须测试SQL注入、XSS等攻击
  • 认证测试: 测试令牌验证、权限控制
  • 数据保护: 测试敏感数据泄露风险
  • 错误处理: 测试错误信息是否泄露敏感数据

安全测试工具

  • OWASP ZAP: 自动化安全扫描
  • npm audit: 依赖漏洞检查
  • 自定义安全测试: 针对业务逻辑的安全测试

性能测试策略

性能测试要求

  • API响应时间: < 100ms (p95)
  • 数据库查询性能: < 50ms (p95)
  • 并发用户数: 支持100+并发用户
  • 资源使用: CPU < 70%, 内存 < 80%

性能测试工具

  • k6: 负载测试
  • autocannon: API性能测试
  • Playwright: E2E性能监控

测试文档标准

测试代码规范

// 良好的测试示例
describe('UserService', () => {
  describe('createUser()', () => {
    it('应该创建新用户并返回用户对象', async () => {
      // Arrange
      const userData = { username: 'testuser', email: 'test@example.com' };

      // Act
      const result = await userService.createUser(userData);

      // Assert
      expect(result).toHaveProperty('id');
      expect(result.username).toBe('testuser');
      expect(result.email).toBe('test@example.com');
    });

    it('应该拒绝重复的用户名', async () => {
      // Arrange
      const existingUser = await createTestUser({ username: 'existing' });

      // Act & Assert
      await expect(
        userService.createUser({ username: 'existing', email: 'new@example.com' })
      ).rejects.toThrow('用户名已存在');
    });
  });
});

测试命名约定

  • 文件名: [module].test.ts[module].integration.test.ts
  • 描述: 使用「应该...」格式描述测试行为
  • 用例: 明确描述测试场景和预期结果

测试用例编写规范

基于订单管理集成测试的经验总结,制定以下测试用例编写规范:

1. 使用真实组件而非模拟组件

原则: 集成测试应尽可能使用真实的组件,而不是过度简化的模拟组件。

示例:

// ❌ 避免:使用过度简化的模拟组件
vi.mock('@d8d/allin-disability-person-management-ui', () => ({
  DisabledPersonSelector: vi.fn(({ open, onOpenChange, onSelect }) => {
    return (
      <div data-testid="disabled-person-selector-mock">
        <button onClick={() => onSelect(mockPerson)}>选择测试人员</button>
      </div>
    );
  }),
}));

// ✅ 推荐:使用真实的组件,模拟其依赖的API
// 在RPC客户端模拟中添加组件所需的API
vi.mock('@d8d/shared-ui-components/utils/hc', () => ({
  rpcClient: vi.fn(() => ({
    searchDisabledPersons: {
      $get: vi.fn(() => Promise.resolve({
        status: 200,
        json: async () => ({
          data: [{
            id: 1,
            name: '测试残疾人',
            // 包含真实组件需要的所有字段
            idCard: '110101199001011234',
            disabilityId: 'D123456',
            // ... 其他字段
          }],
          total: 1
        })
      }))
    }
  }))
}));

2. 理解组件的工作模式

原则: 测试前必须理解真实组件的工作模式(单选/多选、交互方式等)。

示例:

// 订单创建中的残疾人选择器使用多选模式 (mode="multiple")
// 因此测试需要:
// 1. 勾选复选框(而不是点击表格行)
// 2. 点击确认按钮

// 查找并勾选复选框
const checkbox = screen.getByRole('checkbox', { name: '选择' });
await userEvent.click(checkbox);

// 点击确认选择按钮
const confirmButton = screen.getByTestId('confirm-batch-button');
await userEvent.click(confirmButton);

3. 提供完整的模拟数据

原则: 模拟数据应包含真实组件需要的所有字段,避免因字段缺失导致测试失败。

示例:

// 残疾人选择器表格需要显示idCard字段
const mockPerson = {
  id: 1,
  name: '测试残疾人',
  gender: '男',
  idCard: '110101199001011234', // 必须包含的字段
  disabilityId: 'D123456',
  disabilityType: '肢体残疾',
  disabilityLevel: '三级',
  phone: '13800138000',
  province: '北京',
  city: '北京市',
  provinceId: 1,
  cityId: 2,
  isInBlackList: 0
};

4. 使用适当的测试选择器

原则: 优先使用语义化的选择器(role、label),其次使用test-id,避免使用不稳定的选择器。

示例:

// ✅ 推荐:使用role选择器
const checkbox = screen.getByRole('checkbox', { name: '选择' });

// ✅ 推荐:使用test-id选择器
const confirmButton = screen.getByTestId('confirm-batch-button');

// ✅ 推荐:使用文本选择器(当文本稳定时)
const dialogTitle = screen.getByText('选择残疾人');

// ❌ 避免:使用不稳定的CSS类选择器
const button = screen.getByClassName('btn-primary');

5. 处理异步加载和状态变化

原则: 集成测试需要正确处理组件的异步加载和状态变化。

示例:

// 等待组件加载完成
await waitFor(() => {
  expect(screen.getByTestId('disabled-persons-table')).toBeInTheDocument();
});

// 等待组件关闭
await waitFor(() => {
  expect(screen.queryByTestId('disabled-persons-table')).not.toBeInTheDocument();
});

6. 验证完整的用户流程

原则: 集成测试应验证完整的用户流程,而不仅仅是单个操作。

示例:

// 完整的订单创建流程:
// 1. 打开订单表单
// 2. 填写基本信息
// 3. 打开残疾人选择器
// 4. 选择残疾人
// 5. 提交表单
// 6. 验证结果

it('应该成功创建订单并绑定人员', async () => {
  // 1. 打开订单表单
  const createButton = screen.getByTestId('create-order-button');
  await userEvent.click(createButton);

  // 2. 填写基本信息
  const orderNameInput = screen.getByPlaceholderText('请输入订单名称');
  fireEvent.change(orderNameInput, { target: { value: '测试订单' } });

  // 3. 打开残疾人选择器
  const selectPersonsButton = screen.getByTestId('select-persons-button');
  fireEvent.click(selectPersonsButton);

  // 4. 选择残疾人
  await waitFor(() => {
    expect(screen.getByTestId('disabled-persons-table')).toBeInTheDocument();
  });

  const checkbox = screen.getByRole('checkbox', { name: '选择' });
  await userEvent.click(checkbox);

  const confirmButton = screen.getByTestId('confirm-batch-button');
  await userEvent.click(confirmButton);

  // 5. 提交表单
  const submitButton = screen.getByTestId('order-create-submit-button');
  await userEvent.click(submitButton);

  // 6. 验证结果
  await waitFor(() => {
    expect(screen.getByText('订单创建成功')).toBeInTheDocument();
  });
});

7. 使用共享测试工具处理复杂组件

原则: 对于复杂的UI组件(如Radix UI Select),应使用共享UI包中的测试工具函数,而不是直接操作DOM元素。

示例:

// ❌ 避免:直接操作Radix UI Select组件的DOM元素
const selectButton = screen.getByTestId('platform-selector-create');
await userEvent.click(selectButton);
const hiddenSelect = document.querySelector('select[data-radix-select-viewport]');
await userEvent.selectOptions(hiddenSelect, '1');

// ✅ 推荐:使用共享UI包的测试工具函数
import { completeRadixSelectFlow } from '@d8d/shared-ui-components/tests/utils';

// 处理Radix UI Select组件的完整选择流程
await completeRadixSelectFlow('platform-selector-create', '1', { useFireEvent: true });
await completeRadixSelectFlow('company-selector-create', '1', { useFireEvent: true });
await completeRadixSelectFlow('channel-selector-create', '1', { useFireEvent: true });

注意: 共享UI包的测试工具提供了以下功能:

  • completeRadixSelectFlow: 完整的Radix UI Select选择流程
  • findHiddenSelectElement: 查找隐藏的select元素
  • selectRadixOption: 选择Radix UI Select选项
  • waitForRadixSelectEnabled: 等待Select组件启用

这些工具封装了复杂的DOM操作逻辑,提高了测试代码的可维护性和可读性。

8. 清理调试信息

原则: 提交代码前应移除不必要的调试信息(console.log、console.debug)。

示例:

// ❌ 避免:提交包含调试信息的代码
console.log('测试:调用onSelect,人员数据:', mockPerson);
console.debug('所有test ID:', allElements.map(el => el.getAttribute('data-testid')));

// ✅ 推荐:提交前移除调试信息
// 只在开发时临时添加调试信息,完成后立即移除

监控和报告

测试监控指标

  • 测试通过率: > 95%
  • 测试执行时间: < 10分钟(单元+集成)
  • 测试稳定性: 无flaky tests
  • 覆盖率趋势: 持续改进或保持

测试报告要求

  • HTML报告: 详细的覆盖率报告
  • JUnit报告: CI/CD集成
  • 自定义报告: 业务指标测试报告
  • 历史趋势: 测试质量趋势分析

附录

相关文档

工具版本

  • Vitest: 3.2.4
  • Testing Library: 16.3.0
  • Playwright: 1.55.0
  • hono/testing: 内置(Hono 4.8.5)
  • shared-test-util: 1.0.0 (测试基础设施包)
  • TypeORM: 0.3.20 (数据库测试)
  • Redis: 7.0.0 (会话管理测试)

更新日志

日期 版本 描述
2025-12-12 2.10 添加使用共享测试工具处理复杂组件的规范
2025-12-12 2.9 添加测试用例编写规范,基于订单管理集成测试经验
2025-11-11 2.8 更新包测试结构,添加模块化包测试策略
2025-11-09 2.7 更新为monorepo测试架构,清理重复测试文件
2025-10-15 2.6 完成遗留测试文件迁移到统一的tests目录结构
2025-10-14 2.5 重构测试文件结构,统一到tests目录
2025-09-20 2.4 更新版本与主架构文档一致
2025-09-19 1.0 初始版本,基于现有测试基础设施

文档状态: 正式版 下次评审: 2026-01-12