|
|
@@ -3,6 +3,7 @@
|
|
|
## 版本信息
|
|
|
| 版本 | 日期 | 描述 | 作者 |
|
|
|
|------|------|------|------|
|
|
|
+| 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 |
|
|
|
@@ -362,6 +363,187 @@ describe('UserService', () => {
|
|
|
- **描述**: 使用「应该...」格式描述测试行为
|
|
|
- **用例**: 明确描述测试场景和预期结果
|
|
|
|
|
|
+### 测试用例编写规范
|
|
|
+
|
|
|
+基于订单管理集成测试的经验总结,制定以下测试用例编写规范:
|
|
|
+
|
|
|
+#### 1. 使用真实组件而非模拟组件
|
|
|
+**原则**: 集成测试应尽可能使用真实的组件,而不是过度简化的模拟组件。
|
|
|
+
|
|
|
+**示例**:
|
|
|
+```typescript
|
|
|
+// ❌ 避免:使用过度简化的模拟组件
|
|
|
+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. 理解组件的工作模式
|
|
|
+**原则**: 测试前必须理解真实组件的工作模式(单选/多选、交互方式等)。
|
|
|
+
|
|
|
+**示例**:
|
|
|
+```typescript
|
|
|
+// 订单创建中的残疾人选择器使用多选模式 (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. 提供完整的模拟数据
|
|
|
+**原则**: 模拟数据应包含真实组件需要的所有字段,避免因字段缺失导致测试失败。
|
|
|
+
|
|
|
+**示例**:
|
|
|
+```typescript
|
|
|
+// 残疾人选择器表格需要显示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,避免使用不稳定的选择器。
|
|
|
+
|
|
|
+**示例**:
|
|
|
+```typescript
|
|
|
+// ✅ 推荐:使用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. 处理异步加载和状态变化
|
|
|
+**原则**: 集成测试需要正确处理组件的异步加载和状态变化。
|
|
|
+
|
|
|
+**示例**:
|
|
|
+```typescript
|
|
|
+// 等待组件加载完成
|
|
|
+await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('disabled-persons-table')).toBeInTheDocument();
|
|
|
+});
|
|
|
+
|
|
|
+// 等待组件关闭
|
|
|
+await waitFor(() => {
|
|
|
+ expect(screen.queryByTestId('disabled-persons-table')).not.toBeInTheDocument();
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+#### 6. 验证完整的用户流程
|
|
|
+**原则**: 集成测试应验证完整的用户流程,而不仅仅是单个操作。
|
|
|
+
|
|
|
+**示例**:
|
|
|
+```typescript
|
|
|
+// 完整的订单创建流程:
|
|
|
+// 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. 清理调试信息
|
|
|
+**原则**: 提交代码前应移除不必要的调试信息(console.log、console.debug)。
|
|
|
+
|
|
|
+**示例**:
|
|
|
+```typescript
|
|
|
+// ❌ 避免:提交包含调试信息的代码
|
|
|
+console.log('测试:调用onSelect,人员数据:', mockPerson);
|
|
|
+console.debug('所有test ID:', allElements.map(el => el.getAttribute('data-testid')));
|
|
|
+
|
|
|
+// ✅ 推荐:提交前移除调试信息
|
|
|
+// 只在开发时临时添加调试信息,完成后立即移除
|
|
|
+```
|
|
|
+
|
|
|
## 监控和报告
|
|
|
|
|
|
### 测试监控指标
|
|
|
@@ -395,6 +577,7 @@ describe('UserService', () => {
|
|
|
### 更新日志
|
|
|
| 日期 | 版本 | 描述 |
|
|
|
|------|------|------|
|
|
|
+| 2025-12-12 | 2.9 | 添加测试用例编写规范,基于订单管理集成测试经验 |
|
|
|
| 2025-11-11 | 2.8 | 更新包测试结构,添加模块化包测试策略 |
|
|
|
| 2025-11-09 | 2.7 | 更新为monorepo测试架构,清理重复测试文件 |
|
|
|
| 2025-10-15 | 2.6 | 完成遗留测试文件迁移到统一的tests目录结构 |
|
|
|
@@ -405,4 +588,4 @@ describe('UserService', () => {
|
|
|
---
|
|
|
|
|
|
**文档状态**: 正式版
|
|
|
-**下次评审**: 2025-12-19
|
|
|
+**下次评审**: 2026-01-12
|