# 集成测试最佳实践指南

## 概述

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

## 测试类型区分

### 单元测试 (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 集成测试模式

### 基本结构
```typescript
describe('API Integration Tests', () => {
  let app: Hono;
  let apiClient: ApiClient;

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

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

### 请求测试模式
```typescript
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');
});
```

### 认证测试模式
```typescript
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 组件集成测试模式

### 基本渲染测试
```typescript
it('应该渲染所有子组件', () => {
  render(
    <TestWrapper>
      <ComplexComponent />
    </TestWrapper>
  );

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

### 用户交互测试
```typescript
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');
});
```

### 路由集成测试
```typescript
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
```typescript
// 使用内存数据库
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
```typescript
// 使用 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
```typescript
// 使用 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());
```

## 测试数据管理

### 测试数据工厂
```typescript
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 });
```

### 数据清理策略
```typescript
// 策略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();
});
```

## 性能优化

### 测试执行优化
```typescript
// 共享测试资源
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,
    // ...
  }
});
```

### 减少不必要的操作
```typescript
// 避免在每个测试中重新初始化
beforeAll(async () => {
  // 一次性初始化
});

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

## 调试技巧

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

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

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

## 常见问题解决

### 测试不稳定 (Flaky Tests)
- **原因**: 异步操作时序问题
- **解决**: 使用 `waitFor` 和适当的断言

```typescript
// 错误方式
expect(element).toBeInTheDocument(); // 可能尚未渲染

// 正确方式
await waitFor(() => {
  expect(element).toBeInTheDocument();
});
```

### 内存泄漏
- **原因**: 未正确清理资源
- **解决**: 确保 afterEach/afterAll 中清理所有资源

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

### 测试执行缓慢
- **原因**: 过多的真实数据库操作
- **解决**: 使用内存数据库或更好的mock策略

## CI/CD 集成

### 测试配置
```yaml
# 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
```

### 测试报告
```yaml
# 生成测试报告
- 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 规则
```javascript
// .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%

## 附录

### 有用命令
```bash
# 运行集成测试
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*