# 集成测试最佳实践 ## 概述 本文档提供了在项目中编写和维护集成测试的最佳实践指南。集成测试用于验证多个组件或服务之间的协作是否正确。 ## 测试类型区分 ### 单元测试 vs 集成测试 | 测试类型 | 测试范围 | 使用场景 | |---------|---------|---------| | **单元测试** | 单个函数、方法、类 | 验证独立逻辑的正确性 | | **集成测试** | 多个组件、服务协作 | 验证系统各部分集成是否正确 | | **端到端测试** | 完整用户流程 | 验证从用户界面到后端的完整流程 | ## API集成测试模式 ### 基本测试结构 ```typescript describe('API Endpoint Integration', () => { let app: Hono; let apiClient: ApiClient; beforeEach(async () => { // 设置测试环境 vi.clearAllMocks(); // 创建测试应用 app = new Hono(); // 注册路由 const routes = await import('./routes'); routes.default(app); // 创建API客户端 apiClient = createApiClient(app); }); it('should return correct response', async () => { // 设置mock const mockService = require('./service').default; mockService.doSomething.mockResolvedValue({ data: 'test' }); // 执行请求 const response = await apiClient.get('/endpoint'); // 验证结果 expect(response.status).toBe(200); expect(response.data).toEqual({ data: 'test' }); expect(mockService.doSomething).toHaveBeenCalled(); }); }); ``` ### 认证和授权测试 ```typescript describe('Authentication', () => { it('should require authentication', async () => { const response = await apiClient.get('/protected', {}, { authToken: undefined }); expect(response.status).toBe(401); }); it('should require specific roles', async () => { // Mock用户具有不同角色 const response = await apiClient.get('/admin-only'); expect(response.status).toBe(403); }); }); ``` ### 错误处理测试 ```typescript describe('Error Handling', () => { it('should handle service errors gracefully', async () => { const mockService = require('./service').default; mockService.doSomething.mockRejectedValue(new Error('Service unavailable')); const response = await apiClient.get('/endpoint'); expect(response.status).toBe(500); expect(response.data).toMatchObject({ code: 500, message: 'Service unavailable' }); }); it('should handle validation errors', async () => { const response = await apiClient.post('/endpoint', { invalid: 'data' }); expect(response.status).toBe(400); expect(response.data).toHaveProperty('error'); }); }); ``` ## React组件集成测试模式 ### 组件渲染测试 ```typescript describe('Component Integration', () => { it('should render with correct props', () => { render( ); expect(screen.getByText('Test')).toBeInTheDocument(); expect(screen.getByRole('button')).toBeEnabled(); }); it('should handle user interactions', async () => { const user = userEvent.setup(); const handleClick = vi.fn(); render( ); await user.click(screen.getByRole('button')); expect(handleClick).toHaveBeenCalledTimes(1); }); }); ``` ### 路由和导航测试 ```typescript describe('Routing', () => { it('should navigate to correct route', async () => { renderWithRouter(, { route: '/login' }); expect(screen.getByLabelText('Email')).toBeInTheDocument(); expect(screen.getByLabelText('Password')).toBeInTheDocument(); }); it('should handle protected routes', () => { renderWithRouter(, { route: '/admin' }); // 未认证用户应该被重定向 expect(screen.getByText('Redirecting to login...')).toBeInTheDocument(); }); }); ``` ### 状态管理测试 ```typescript describe('State Management', () => { it('should update UI based on state changes', async () => { render( ); // 初始状态 expect(screen.getByText('Loading...')).toBeInTheDocument(); // 等待数据加载 await waitFor(() => { expect(screen.getByText('John Doe')).toBeInTheDocument(); }); }); }); ``` ## Mock策略 ### 数据库Mock ```typescript // 使用内存数据库 const testDataSource = new DataSource({ type: 'better-sqlite3', database: ':memory:', synchronize: true, entities: [User, Role] }); // 或者使用mock repository const mockRepo = { find: vi.fn().mockResolvedValue([]), findOne: vi.fn().mockResolvedValue(null), save: vi.fn().mockResolvedValue({ id: 1 }) }; ``` ### 外部服务Mock ```typescript // 使用MSW mock HTTP请求 import { setupServer } from 'msw/node'; import { rest } from 'msw'; const server = setupServer( rest.get('/api/users', (req, res, ctx) => { return res(ctx.json([{ id: 1, name: 'Test User' }])); }) ); // 或者使用vi.mock vi.mock('../external-service', () => ({ fetchData: vi.fn().mockResolvedValue({ data: 'test' }) })); ``` ### 认证Mock ```typescript // Mock JWT验证 vi.mock('../auth/middleware', () => ({ authMiddleware: vi.fn().mockImplementation((c, next) => { c.set('user', { id: 1, username: 'testuser' }); return next(); }) })); ``` ## 测试数据管理 ### 测试数据工厂 ```typescript export function createTestUser(overrides: Partial = {}): User { return { id: 1, username: 'testuser', email: 'test@example.com', password: 'hashed_password', createdAt: new Date(), updatedAt: new Date(), ...overrides }; } // 使用工厂创建测试数据 const adminUser = createTestUser({ username: 'admin', roles: ['admin'] }); const inactiveUser = createTestUser({ status: 'inactive' }); ``` ### 测试数据清理 ```typescript afterEach(async () => { // 清理测试数据 await testDataSource.getRepository(User).clear(); await testDataSource.getRepository(Role).clear(); }); afterAll(async () => { // 关闭数据库连接 await testDataSource.destroy(); }); ``` ## 性能优化 ### 测试并行化 ```typescript // 在vitest.config.ts中配置 export default defineConfig({ test: { maxThreads: 4, minThreads: 2, fileParallelism: true } }); ``` ### 测试数据隔离 ```typescript // 使用事务确保测试隔离 describe('User Tests', () => { let dataSource: DataSource; let queryRunner: QueryRunner; beforeEach(async () => { dataSource = await createTestDataSource(); queryRunner = dataSource.createQueryRunner(); await queryRunner.startTransaction(); }); afterEach(async () => { await queryRunner.rollbackTransaction(); await queryRunner.release(); }); afterAll(async () => { await dataSource.destroy(); }); }); ``` ## 常见陷阱和解决方案 ### 1. 测试相互干扰 **问题**: 一个测试修改了全局状态,影响其他测试 **解决方案**: 使用`beforeEach`和`afterEach`清理状态 ### 2. 测试执行缓慢 **问题**: 集成测试执行时间过长 **解决方案**: 使用内存数据库,mock外部服务 ### 3. 测试不稳定 **问题**: 测试有时通过,有时失败 **解决方案**: 避免依赖时间、随机数,使用固定测试数据 ### 4. Mock过于复杂 **问题**: Mock代码比实际代码还复杂 **解决方案**: 使用工具函数简化mock创建 ## 代码质量检查 ### 测试覆盖率目标 - 行覆盖率: > 70% - 分支覆盖率: > 70% - 函数覆盖率: > 70% - 语句覆盖率: > 70% ### 代码审查清单 - [ ] 测试名称清晰描述预期行为 - [ ] 使用恰当的断言 - [ ] 包含错误场景测试 - [ ] 测试数据准备充分 - [ ] 测试清理逻辑完整 - [ ] 没有重复的测试代码 - [ ] 遵循AAA模式(Arrange-Act-Assert) ## 工具和配置 ### 推荐工具 - **测试框架**: Vitest - **API测试**: Supertest + 自定义API客户端 - **组件测试**: Testing Library + Happy DOM - **HTTP Mock**: MSW (Mock Service Worker) - **数据库**: SQLite内存数据库 ### 配置文件示例 ```typescript // vitest.config.ts export default defineConfig({ test: { environment: 'node', include: ['**/__integration_tests__/**/*.test.ts'], setupFiles: ['./src/test/setup.ts'], globals: true } }); ``` ## 总结 集成测试是确保系统各部分正确协作的关键。遵循这些最佳实践可以编写出可靠、可维护的集成测试。记住测试的目标是增加信心,而不是追求100%的覆盖率。专注于测试关键业务逻辑和集成点。