integration-testing-best-practices.md 8.7 KB

集成测试最佳实践指南

概述

本文档提供了项目集成测试的最佳实践、模式和指南,帮助开发团队编写高质量、可维护的集成测试。

测试类型区分

单元测试 (Unit Tests)

  • 范围: 单个函数、类或组件
  • 目标: 验证独立单元的 correctness
  • 位置: src/**/__tests__/**/*.test.{ts,tsx}

集成测试 (Integration Tests)

  • 范围: 多个组件/服务协作
  • 目标: 验证模块间集成和交互
  • 位置: src/**/__integration_tests__/**/*.integration.test.{ts,tsx}

E2E测试 (End-to-End Tests)

  • 范围: 完整用户流程
  • 目标: 验证端到端业务流程
  • 位置: tests/e2e/**/*.test.{ts,tsx}

API 集成测试模式

基本结构

describe('API Integration Tests', () => {
  let app: Hono;
  let apiClient: ApiClient;

  beforeEach(async () => {
    // 设置测试环境
    app = createTestApp();
    apiClient = createApiClient(app);
  });

  afterEach(() => {
    // 清理资源
  });
});

请求测试模式

it('应该返回正确的状态码和数据', async () => {
  const response = await apiClient.get('/api/endpoint');

  expect(response.status).toBe(200);
  expect(response.data).toMatchObject(expectedData);
});

it('应该处理错误情况', async () => {
  const response = await apiClient.get('/api/invalid-endpoint');

  expect(response.status).toBe(404);
  expect(response.data).toHaveProperty('error');
});

认证测试模式

it('需要认证的端点应该验证令牌', async () => {
  apiClient.clearAuthToken();

  const response = await apiClient.get('/api/protected');
  expect(response.status).toBe(401);
});

it('有效令牌应该允许访问', async () => {
  apiClient.setAuthToken('valid-token');

  const response = await apiClient.get('/api/protected');
  expect(response.status).toBe(200);
});

React 组件集成测试模式

基本渲染测试

it('应该渲染所有子组件', () => {
  render(
    <TestWrapper>
      <ComplexComponent />
    </TestWrapper>
  );

  expect(screen.getByText('Expected Text')).toBeInTheDocument();
  expect(screen.getByRole('button')).toBeInTheDocument();
});

用户交互测试

it('应该处理用户输入和提交', async () => {
  const user = userEvent.setup();
  const onSubmit = vi.fn();

  render(
    <TestWrapper>
      <FormComponent onSubmit={onSubmit} />
    </TestWrapper>
  );

  await user.type(screen.getByLabelText('Email'), 'test@example.com');
  await user.click(screen.getByRole('button', { name: 'Submit' }));

  expect(onSubmit).toHaveBeenCalledWith('test@example.com');
});

路由集成测试

it('应该正确处理导航', async () => {
  const user = userEvent.setup();

  render(
    <TestQueryProvider>
      <TestRouter initialPath="/home">
        <App />
      </TestRouter>
    </TestQueryProvider>
  );

  await user.click(screen.getByText('Go to Settings'));
  expect(screen.getByText('Settings Page')).toBeInTheDocument();
});

Mock 策略

数据库 Mock

// 使用内存数据库
const testDataSource = new DataSource({
  type: 'better-sqlite3',
  database: ':memory:',
  entities: [User, Role],
  synchronize: true,
});

// 使用事务回滚确保测试隔离
beforeEach(async () => {
  await testDataSource.initialize();
});

afterEach(async () => {
  await testDataSource.destroy();
});

服务 Mock

// 使用 vi.mock()
vi.mock('../services/external-service', () => ({
  ExternalService: {
    fetchData: vi.fn().mockResolvedValue(mockData),
    sendData: vi.fn().mockResolvedValue({ success: true }),
  }
}));

// 或者使用自定义mock工具
import { ServiceMocks } from '../__test_utils__/service-mocks';

beforeEach(() => {
  ServiceMocks.setupForSuccess();
});

HTTP 请求 Mock

// 使用 MSW (Mock Service Worker)
import { setupServer } from 'msw/node';
import { rest } from 'msw';

const server = setupServer(
  rest.get('/api/users', (req, res, ctx) => {
    return res(ctx.json({ users: [] }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

测试数据管理

测试数据工厂

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 });

数据清理策略

// 策略1: 事务回滚 (推荐)
describe('User API', () => {
  let dataSource: DataSource;

  beforeEach(async () => {
    dataSource = await TestDatabase.initialize();
    await dataSource.startTransaction();
  });

  afterEach(async () => {
    await dataSource.rollbackTransaction();
    await dataSource.destroy();
  });
});

// 策略2: 清理数据库
afterEach(async () => {
  await dataSource.getRepository(User).clear();
  await dataSource.getRepository(Role).clear();
});

性能优化

测试执行优化

// 共享测试资源
let sharedDataSource: DataSource;

beforeAll(async () => {
  sharedDataSource = await TestDatabase.initialize();
});

afterAll(async () => {
  await sharedDataSource.destroy();
});

// 使用并行测试执行
// 在 vitest.config.ts 中配置
export default defineConfig({
  test: {
    maxThreads: 4,
    minThreads: 2,
    // ...
  }
});

减少不必要的操作

// 避免在每个测试中重新初始化
beforeAll(async () => {
  // 一次性初始化
});

// 使用mock代替真实操作
vi.mock('heavy-operation-module');

调试技巧

测试调试

// 添加调试输出
test('debug test', async () => {
  console.log('Starting test...');
  // 测试代码
  console.log('Test completed');
});

// 使用 --test-timeout 参数增加超时时间
// vitest --test-timeout=30000

网络请求调试

// 记录所有API请求
beforeEach(() => {
  vi.spyOn(global, 'fetch').mockImplementation(async (input, init) => {
    console.log('API Request:', input, init);
    return mockResponse;
  });
});

常见问题解决

测试不稳定 (Flaky Tests)

  • 原因: 异步操作时序问题
  • 解决: 使用 waitFor 和适当的断言

    // 错误方式
    expect(element).toBeInTheDocument(); // 可能尚未渲染
    
    // 正确方式
    await waitFor(() => {
    expect(element).toBeInTheDocument();
    });
    

内存泄漏

  • 原因: 未正确清理资源
  • 解决: 确保 afterEach/afterAll 中清理所有资源

    afterEach(async () => {
    await cleanupTestResources();
    vi.clearAllMocks();
    vi.resetAllMocks();
    });
    

测试执行缓慢

  • 原因: 过多的真实数据库操作
  • 解决: 使用内存数据库或更好的mock策略

CI/CD 集成

测试配置

# GitHub Actions 示例
name: Integration Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: test_db
        ports:
          - 5432:5432
        options: >-
          --health-cmd="pg_isready"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run test:integration

测试报告

# 生成测试报告
- run: npm run test:integration -- --reporter=junit --outputFile=test-results.xml
- uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: test-results.xml

代码质量检查

ESLint 规则

// .eslintrc.js
module.exports = {
  rules: {
    'testing-library/await-async-utils': 'error',
    'testing-library/no-await-sync-events': 'error',
    'testing-library/no-debugging-utils': 'warn',
    'testing-library/no-dom-import': 'error',
  }
};

测试覆盖率要求

  • API 端点: ≥ 80%
  • 关键业务逻辑: ≥ 90%
  • 错误处理路径: ≥ 70%
  • 整体覆盖率: ≥ 75%

附录

有用命令

# 运行集成测试
npm run test:integration

# 运行特定测试文件
npm run test:integration -- src/server/__integration_tests__/users.integration.test.ts

# 调试模式
npm run test:integration -- --inspect-brk

# 生成覆盖率报告
npm run test:integration -- --coverage

推荐工具

  • Vitest: 测试框架
  • Testing Library: React 测试工具
  • MSW: HTTP Mock 工具
  • better-sqlite3: 内存数据库
  • Docker: 测试数据库容器

最后更新: 2025-09-15 版本: 1.0