Files
hms/docs/superpowers/specs/2026-04-26-test-coverage-strategy-design.md
iven 5b81a0051f docs: 修正测试策略 spec 的事实性错误
修正 spec review 发现的问题:
- C-1: TestDb 实际是本地 PostgreSQL 隔离,非 Testcontainers
- C-2: E2E 已有 4 spec/10 测试,非零测试
- 补充 6 个遗漏的 service(alert/daily_monitoring/critical_value_threshold 等)
- 增加 Phase 0 基础设施搭建
- 修正 CI 配置(增加 PostgreSQL service、验证链)
- 补充 5 个遗漏风险项和回退策略
- 统一"全量 80%"目标的准确含义
2026-04-27 00:21:02 +08:00

368 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 测试覆盖率提升策略设计规格
> 日期: 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 模块,减少启动开销)
- AppStateDatabaseConnection + 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: 高风险 ServiceWeek 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: 中风险 ServiceWeek 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 + DTOWeek 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 setupE2E 用)
│ └── 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 speclogin/users/plugins/tenant-isolation10 个测试。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 19 周)的目标是后端 80% + 前端 60%,前端剩余部分在后续迭代中持续补齐。每 Phase 结束后复盘实际覆盖率,必要时调整计划。