Просмотр исходного кода

📚 docs(testing): 添加测试用例编写规范到测试策略文档

- 添加"测试用例编写规范"章节,基于订单管理集成测试经验
- 包含7个编写规范:
  1. 使用真实组件而非模拟组件
  2. 理解组件的工作模式
  3. 提供完整的模拟数据
  4. 使用适当的测试选择器
  5. 处理异步加载和状态变化
  6. 验证完整的用户流程
  7. 清理调试信息
- 更新版本到2.9,更新下次评审日期

🤖 Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 1 неделя назад
Родитель
Сommit
5711a4eea1
1 измененных файлов с 184 добавлено и 1 удалено
  1. 184 1
      docs/architecture/testing-strategy.md

+ 184 - 1
docs/architecture/testing-strategy.md

@@ -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