| 版本 | 日期 | 描述 | 作者 |
|---|---|---|---|
| 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架构和现有的测试基础设施。测试策略遵循测试金字塔模型,确保代码质量、功能稳定性和系统可靠性。
项目已重构为模块化包架构,测试架构相应调整为:
项目采用分层测试架构,每个包独立测试:
packages/shared-*/tests/unit/**/*.test.tspackages/*-module/tests/unit/**/*.test.tspackages/server/tests/unit/**/*.test.tsweb/tests/unit/**/*.test.{ts,tsx}packages/*-module/tests/integration/**/*.test.tspackages/server/tests/integration/**/*.test.ts (模块集成测试)web/tests/integration/**/*.test.{ts,tsx}web/tests/e2e/**/*.test.{ts,tsx}// 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}'
],
// ... 其他配置
}
}
]
}
});
# 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% | - |
// 测试数据工厂模式
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 });
# 运行所有基础设施包测试
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
# 运行所有测试
cd packages/server && pnpm test
# 运行集成测试
cd packages/server && pnpm test:integration
# 生成覆盖率报告
cd packages/server && pnpm test:coverage
# 运行所有测试
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
// 良好的测试示例
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基于订单管理集成测试的经验总结,制定以下测试用例编写规范:
原则: 集成测试应尽可能使用真实的组件,而不是过度简化的模拟组件。
示例:
// ❌ 避免:使用过度简化的模拟组件
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
})
}))
}
}))
}));
原则: 测试前必须理解真实组件的工作模式(单选/多选、交互方式等)。
示例:
// 订单创建中的残疾人选择器使用多选模式 (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);
原则: 模拟数据应包含真实组件需要的所有字段,避免因字段缺失导致测试失败。
示例:
// 残疾人选择器表格需要显示idCard字段
const mockPerson = {
id: 1,
name: '测试残疾人',
gender: '男',
idCard: '110101199001011234', // 必须包含的字段
disabilityId: 'D123456',
disabilityType: '肢体残疾',
disabilityLevel: '三级',
phone: '13800138000',
province: '北京',
city: '北京市',
provinceId: 1,
cityId: 2,
isInBlackList: 0
};
原则: 优先使用语义化的选择器(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');
原则: 集成测试需要正确处理组件的异步加载和状态变化。
示例:
// 等待组件加载完成
await waitFor(() => {
expect(screen.getByTestId('disabled-persons-table')).toBeInTheDocument();
});
// 等待组件关闭
await waitFor(() => {
expect(screen.queryByTestId('disabled-persons-table')).not.toBeInTheDocument();
});
原则: 集成测试应验证完整的用户流程,而不仅仅是单个操作。
示例:
// 完整的订单创建流程:
// 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();
});
});
原则: 对于复杂的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操作逻辑,提高了测试代码的可维护性和可读性。
原则: 提交代码前应移除不必要的调试信息(console.log、console.debug)。
示例:
// ❌ 避免:提交包含调试信息的代码
console.log('测试:调用onSelect,人员数据:', mockPerson);
console.debug('所有test ID:', allElements.map(el => el.getAttribute('data-testid')));
// ✅ 推荐:提交前移除调试信息
// 只在开发时临时添加调试信息,完成后立即移除
| 日期 | 版本 | 描述 |
|---|---|---|
| 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