# 测试覆盖率提升策略设计规格 > 日期: 2026-04-26 | 状态: draft | 主题: 测试覆盖率 2% → 80% ## 1. 背景与目标 ### 1.1 现状 HMS 健康管理平台当前测试覆盖极低: - **后端**: 约 461 单元测试 + 54 集成测试(注:wiki/testing.md 记录 93 个,差距因统计口径不同 — 此处含所有 crate 的 `#[test]`/`#[tokio::test]` 标记),但 erp-health 34 entity 中 27 个 service 零单元测试,erp-ai 整个 crate 零测试 - **前端**: 138 个源文件仅 3 个单元测试文件(覆盖率 2.2%),API 层/Store 层/hooks 全部无测试 - **E2E**: Playwright 已配置(含 auth fixture `apps/web/e2e/auth.fixture.ts`),已有 4 个 spec、10 个测试用例(login/users/plugins/tenant-isolation),但健康模块零 E2E 覆盖 ### 1.2 目标 - **全量覆盖率**: 后端所有层(service/handler/entity/dto)+ 前端所有层(API/Store/hooks/页面)达到 80% - **CI 增量门禁**: 新增/修改文件覆盖率 ≥ 80% 才允许合并 - **时间线**: 9 周分阶段完成 ### 1.3 决策记录 | 决策 | 选择 | 理由 | |------|------|------| | 覆盖范围 | 全量 80% | 医疗场景需要全面质量保证 | | CI 门禁 | 增量门禁 | 不阻塞现有开发,只要求新增代码 | | 后端策略 | 集成测试为主(本地 PostgreSQL 隔离数据库) | 最接近生产,SeaORM mock 不成熟;当前 TestDb 已连接本地 PostgreSQL | | 整体方案 | 分层渐进式(方案 A) | 按风险密度排序,与增量门禁配合 | --- ## 2. 后端测试策略 ### 2.1 测试分层模型 ``` ┌─────────────────────────────────────────┐ │ Integration Tests (本地 PG 隔离) │ ← 30 service + 16 handler │ erp-server/tests/integration/ │ ├─────────────────────────────────────────┤ │ Unit Tests (tokio::test / #[test]) │ ← validation/crypto/masking │ 内联 #[cfg(test)] mod tests │ ├─────────────────────────────────────────┤ │ Pure Function Tests │ ← DTO 转换、枚举、工具函数 │ 零依赖,毫秒级 │ └─────────────────────────────────────────┘ ``` ### 2.2 TestDb 基础设施增强 当前 `crates/erp-server/tests/integration/test_db.rs` 已有 `TestDb` struct,连接**本地 PostgreSQL** 创建临时数据库(不依赖 Docker/Testcontainers,与开发环境一致)。需要增强为: **TestApp struct** — 封装完整测试环境: - TestDb(本地 PostgreSQL 隔离数据库 + 自动迁移) - Axum Router(仅注册 erp-health 模块,减少启动开销) - AppState(DatabaseConnection + EventBus + Crypto,使用测试专用密钥) - HTTP 客户端(使用 `tower::ServiceExt`,无需真实 TCP 监听,测试更快速稳定) **TestFixture 工厂** — 预构建测试数据: - `create_tenant(name)` → 创建租户 + 返回 TenantContext - `create_user(tenant_id, role)` → 创建用户 + 返回 JWT - `create_patient(tenant_id, ...)` → 创建患者档案 - `create_doctor(tenant_id, ...)` → 创建医生档案 - `create_schedule(tenant_id, doctor_id, ...)` → 创建排班 **并行隔离** — 每个测试使用独立的 `tenant_id`,支持并行执行。 ### 2.3 Phase 分配 #### Phase 0: 测试基础设施搭建(Week 1 前 2 天) | 任务 | 产出 | |------|------| | TestApp struct 实现 | TestDb + Axum Router + AppState + tower ServiceExt 客户端 | | TestFixture 工厂 | create_tenant/create_user/create_patient/create_doctor 工厂函数 | | `@vitest/coverage-v8` 安装配置 | 前端覆盖率报告可用 | | MSW v2 初始配置 | handlers.ts + server.ts 基础框架 | | `cargo-llvm-cov` 安装验证 | 后端增量覆盖率命令可用(配合自定义 git diff 脚本过滤) | #### Phase 1: 高风险 Service(Week 1-2) | Service | 测试重点 | 估计测试数 | |---------|---------|-----------| | `points_service` | FIFO 积分消费、余额不足拒绝、并发消费安全性、签到积分 | 12 | | `dialysis_service` | PII 字段加密存储、HMAC 索引查询、CRUD 完整性 | 8 | | `alert_engine` | 规则评估逻辑、cooldown 检查、危急值触发 | 8 | | `alert_rule_service` | 规则 CRUD、阈值验证、启用/禁用 | 6 | | `alert_service` | 告警 CRUD、状态变更、批量确认 | 5 | | `device_reading_service` | 批量插入、降采样聚合、数据范围查询 | 8 | #### Phase 2: 中风险 Service(Week 3-4) | Service | 测试重点 | 估计测试数 | |---------|---------|-----------| | `patient_service` | CRUD + 家属管理 + 标签管理 + 健康摘要 | 10 | | `appointment_service` | 创建预约 CAS + 状态变更 + 取消释放额度 | 8 | | `follow_up_service` | 状态机 6 种转换 + 任务分配 + 执行记录 | 8 | | `consultation_service` | 会话 CRUD + 消息收发 + 状态变更 | 5 | | `doctor_service` | CRUD + 排班关联 | 4 | | erp-ai 基础测试 | 提示模板 CRUD + 分析记录 + 使用统计 | 12 | #### Phase 3: 低风险 Service + Handler + DTO(Week 5-6) | Service | 测试重点 | 估计测试数 | |---------|---------|-----------| | `article_service` | CRUD + 审核状态流转 | 5 | | `article_category_service` | CRUD + 层级管理 | 4 | | `article_tag_service` | CRUD + 关联管理 | 3 | | `offline_event_service` | CRUD + 报名管理 | 4 | | `consent_service` | CRUD + 签署状态 | 3 | | `diagnosis_service` | CRUD + ICD 编码验证 | 3 | | `daily_monitoring_service` | 日常监测 CRUD + 阈值校验 | 4 | | `critical_value_threshold_service` | 阈值 CRUD + 范围校验 | 3 | | `stats_service` | 各维度统计查询正确性 | 5 | | `health_data_service` | 体征/化验/体检 CRUD + 趋势计算 | 8 | | `trend_service` | 已有 14 个内联测试,补充集成测试 | 3 | | `masking` | 已有 14 个内联测试,保持 | 0 | | handler 层 | 16 个 handler 的 HTTP 层测试 | 16 | | DTO 转换 | 请求/响应 DTO 序列化/反序列化 | 10 | > 注:所有健康模块集成测试集中在 `erp-server/tests/integration/` 下,通过 `cargo test -p erp-server` 运行。erp-health 的内联单元测试(validation/crypto/masking/trend)通过 `cargo test -p erp-health` 运行。 ### 2.4 测试命名规范 ``` test_{功能}_{场景}_{预期结果} 例: test_points_consume_fifo_balance_deducted test_patient_create_duplicate_id_card_returns_conflict test_appointment_create_exceeds_max_returns_error test_alert_engine_cooldown_prevents_duplicate test_dialysis_create_pii_fields_encrypted ``` ### 2.5 测试文件组织 ``` crates/erp-server/tests/integration/ ├── test_db.rs # TestDb + TestApp 基础设施 ├── test_fixture.rs # TestFixture 工厂 ├── auth_tests.rs # 已有 ├── workflow_tests.rs # 已有 ├── health_patient_tests.rs # 已有(扩展) ├── health_pii_encryption_tests.rs # 已有(保持) ├── health_appointment_tests.rs # 已有(扩展) ├── health_points_tests.rs # 新增 ├── health_dialysis_tests.rs # 新增 ├── health_alert_tests.rs # 新增 ├── health_device_reading_tests.rs # 新增 ├── health_follow_up_tests.rs # 新增 ├── health_consultation_tests.rs # 新增 ├── health_doctor_tests.rs # 新增 ├── health_article_tests.rs # 新增 ├── health_stats_tests.rs # 新增 ├── health_data_tests.rs # 新增 ├── ai_prompt_template_tests.rs # 新增 ├── ai_analysis_tests.rs # 新增 └── ai_usage_tests.rs # 新增 ``` --- ## 3. 前端测试策略 ### 3.1 测试分层模型 ``` ┌─────────────────────────────────────────┐ │ E2E Tests (Playwright) │ ← 5 个关键用户流程 ├─────────────────────────────────────────┤ │ Page Integration Tests │ ← 8 个核心页面 │ vitest + @testing-library/react │ ├─────────────────────────────────────────┤ │ Store/Hook Unit Tests │ ← 5 stores + 3 hooks │ vitest │ ├─────────────────────────────────────────┤ │ API Layer Tests │ ← client.ts + 7 health API │ vitest + MSW │ └─────────────────────────────────────────┘ ``` ### 3.2 测试工具链 | 工具 | 用途 | 状态 | |------|------|------| | Vitest | 单元测试 + 组件测试 | ✅ 已配置 | | @testing-library/react | 组件渲染测试 | ✅ 已有 | | MSW (mock-service-worker) | API mock | ❌ 需新增 | | Playwright | E2E 测试 | ✅ 已配置 + auth fixture | | @vitest/coverage-v8 | 覆盖率报告 | ❌ 需新增 | ### 3.3 MSW Mock 策略 建立 `apps/web/src/test/` 目录: ``` apps/web/src/test/ ├── setup.ts # 测试全局 setup(已有,需增强) ├── mocks/ │ ├── handlers.ts # 统一 API mock handlers │ ├── server.ts # MSW server setup │ ├── browser.ts # MSW browser setup(E2E 用) │ └── fixtures/ # 测试数据 JSON │ ├── patients.json │ ├── appointments.json │ └── ... └── utils/ ├── render-with-providers.tsx # 包含 Router + Store 的 render wrapper └── wait-for.ts # 异步等待工具 ``` ### 3.4 Phase 4: 前端补测(Week 7-9) #### Week 7: API 层 + Store 层 | 文件 | 测试重点 | 测试数 | |------|---------|--------| | `api/client.ts` | token 主动刷新、401 被动刷新、并发请求队列、缓存命中/过期 | 12 | | `api/health/patients.ts` | CRUD 参数正确性、分页参数、错误映射 | 5 | | `api/health/appointments.ts` | 创建预约参数、状态变更、排班查询 | 4 | | `api/health/*.ts`(其余 5 个) | CRUD 基础覆盖 | 10 | | `stores/auth.ts` | login/logout/refresh 权限列表 | 5 | | `stores/plugin.ts` | 插件列表加载、菜单生成、去重 | 5 | | `stores/health.ts` | 名称缓存、批量解析 | 3 | | `stores/message.ts` | SSE 连接、未读计数、乐观更新 | 4 | #### Week 8: Hooks + 核心页面组件 | 文件 | 测试重点 | 测试数 | |------|---------|--------| | `hooks/useApiRequest.ts` | loading/error 状态、重试、取消 | 5 | | `hooks/usePaginatedData.ts` | 分页加载、刷新、参数变更 | 4 | | `hooks/usePermission.ts` | 权限检查、admin 跳过 | 3 | | `pages/health/PatientList.tsx` | 列表渲染、搜索、新建弹窗 | 5 | | `pages/health/AppointmentList.tsx` | 列表渲染、新建预约、状态变更 | 5 | | `pages/health/ConsultationList.tsx` | 列表渲染、行点击导航 | 3 | | `pages/health/FollowUpTaskList.tsx` | 列表渲染、状态筛选 | 3 | | `pages/health/StatisticsDashboard.tsx` | 数据加载、卡片渲染 | 3 | #### Week 9: Playwright E2E(健康模块专项) 已有 4 个基础 ERP spec(login/users/plugins/tenant-isolation,10 个测试)。Week 9 新增健康模块 E2E: | 测试场景 | 覆盖流程 | |---------|---------| | 患者管理 CRUD | 登录 → 创建患者 → 编辑 → 查看 → 搜索 | | 预约流程 | 选医生 → 查排班 → 创建预约 → 取消预约 | | 随访任务 | 创建随访 → 分配 → 执行 → 完成 | | 咨询流程 | 创建咨询 → 发送消息 → 关闭 | | 数据统计 | 查看统计仪表板 → 导出 | > 注:E2E 测试依赖前后端同时启动。Playwright 配置了 `webServer.command: pnpm dev`(前端),但需确保后端服务已运行。在 CI 中需同时启动后端(`cargo run`)+ 前端(`pnpm dev`)。 --- ## 4. CI 门禁策略 ### 4.1 增量覆盖率门禁 **后端** — 使用 `cargo-llvm-cov` 配合自定义脚本: ```bash # 安装: cargo install cargo-llvm-cov # 全量测试 cargo llvm-cov --workspace --lcov --output-path lcov.info # 增量检查(自定义脚本,基于 git diff 过滤变更文件) cargo llvm-cov --workspace --json | python3 scripts/coverage-diff-check.py --threshold=80 ``` > 注:`cargo-llvm-cov` 原生不支持 `--modified-files-only`,需要自定义脚本实现增量过滤。Phase 0 中需验证可行性。 **前端** — 使用 `@vitest/coverage-v8`: ```bash cd apps/web && pnpm vitest run --coverage # 增量检查通过自定义脚本过滤变更文件 ``` ### 4.2 CI 流水线 > **前置条件**: 项目当前无 CI 配置(`.github/workflows/` 不存在),Phase 0 需创建初始 workflow。 ```yaml # .github/workflows/test.yml on: pull_request jobs: backend-test: runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: test_password POSTGRES_DB: erp_test ports: ["5432:5432"] options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: ERP__DATABASE__URL: postgres://postgres:test_password@localhost:5432/erp_test ERP__JWT__SECRET: ci-test-jwt-secret steps: - uses: actions/checkout@v4 - cargo test --workspace - cargo llvm-cov --workspace --json frontend-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - cd apps/web && pnpm install && pnpm test:ci # E2E: 仅 main 分支或手动触发 e2e-test: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest services: postgres: # 同上 steps: - cargo run & # 后台启动后端 - cd apps/web && pnpm dev & # 后台启动前端 - sleep 10 # 等待服务就绪 - pnpm exec playwright test ``` > CI 流水线需包含 CLAUDE.md 要求的完整验证链:`cargo check` → `cargo test` → `cargo clippy` → `pnpm build`。 ### 4.3 里程碑 | 时间点 | 门禁要求 | |--------|---------| | Week 6 后 | 后端增量门禁上线 | | Week 9 后 | 前端增量门禁上线 | | Week 12 后 | 全量覆盖率报告 + 目标复盘 | --- ## 5. 风险与缓解 | 风险 | 影响 | 缓解措施 | |------|------|---------| | 本地 PG 并行测试连接池耗尽 | 多个 TestDb 实例同时创建可能超限 | 限制并行度 `--test-threads=2`;CI 环境单独配置 | | 测试 panic 后残留临时数据库 | 长期累积占用磁盘 | TestApp Drop 时强制清理;定期脚本清理 `test_*` 前缀数据库 | | 并行迁移竞争 | 多个 TestDb 同时 `Migrator::up` | 每个测试用独立数据库名(UUID),互不影响 | | MSW mock 与真实 API 不一致 | 测试通过但生产出错 | 关键流程 E2E 兜底;MSW handlers 从 OpenAPI spec 自动生成 | | 补测与功能开发冲突 | merge conflict | 测试文件独立于业务代码;集成测试集中在 erp-server/tests/ | | 前端 9 周达不到 80% | 延期 | 接受"后端 80% + 前端 60%"作为 Phase 1 目标,前端在后续迭代持续补齐 | | erp-ai 依赖外部 AI API | 测试不稳定 | mock 外部 AI 调用,仅测试内部逻辑 | | MSW 与 axios 缓存 adapter 冲突 | mock 拦截异常 | MSW 在 service worker 层拦截,绕过 axios adapter;需验证兼容性 | | E2E 测试后端未启动失败 | 测试环境不可用 | CI 中先启动后端再运行 E2E;本地用 dev.ps1 一键启动 | | 目标不可达时无回退方案 | 计划失控 | 每 Phase 结束复盘覆盖率;如 Phase 2 后仅 50%,则缩减 Phase 3 范围或延长时间线 | --- ## 6. 成功指标 | 指标 | Week 6 目标 | Week 9 目标 | |------|------------|------------| | 后端 service 测试覆盖率 | ≥ 80% | ≥ 85% | | 后端全量测试覆盖率 | ≥ 70% | ≥ 80% | | 前端 API+Store 测试覆盖率 | - | ≥ 80% | | 前端全量测试覆盖率 | - | ≥ 60%(Phase 1 目标,后续迭代持续提升到 80%) | | E2E 测试用例数(健康模块) | - | ≥ 5 个新 spec | | CI 增量门禁 | 后端上线 | 前后端上线 | > **目标澄清**: "全量 80%"是最终目标。Phase 1(9 周)的目标是后端 80% + 前端 60%,前端剩余部分在后续迭代中持续补齐。每 Phase 结束后复盘实际覆盖率,必要时调整计划。