Files
hms/docs/superpowers/plans/2026-04-26-test-coverage-strategy.md
iven 2defbd7ab3
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
docs: 测试覆盖率提升实施计划
基于 spec 的 5 Phase + CI 门禁实施计划:
- Phase 0: TestApp + TestFixture + MSW + 覆盖率工具 + CI(2天)
- Phase 1: 高风险 service 测试 — points/dialysis/alert/device_reading(Week 1-2)
- Phase 2: 中风险 service 测试 — patient/appointment/follow_up/consultation/doctor/ai(Week 3-4)
- Phase 3: 低风险 service + handler + DTO + 后端增量门禁(Week 5-6)
- Phase 4: 前端补测 — API/Store/hooks/pages/E2E(Week 7-9)
共 43 个 Task,预计新增 ~230 个测试用例
2026-04-27 00:25:30 +08:00

545 lines
18 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.
# 测试覆盖率提升实施计划
> 设计规格: `docs/superpowers/specs/2026-04-26-test-coverage-strategy-design.md`
> 日期: 2026-04-26 | 状态: draft | 总周期: 9 周(+ Phase 0: 2 天)
---
## Phase 0: 测试基础设施搭建2 天)
### Task 1: TestApp struct 实现
**目标**: 在现有 `TestDb` 基础上封装 `TestApp`,提供完整测试环境。
**涉及文件**:
- 修改: `crates/erp-server/tests/integration/test_db.rs`
**详细步骤**:
1.`test_db.rs` 中新增 `TestApp` struct
```rust
pub struct TestApp {
test_db: TestDb,
health_state: HealthState,
tenant_id: Uuid,
operator_id: Uuid,
}
```
2. 实现 `TestApp::new()`:
- 调用 `TestDb::new()` 创建隔离数据库
- 构建 `HealthState { db, event_bus: EventBus::new(100), crypto: PiiCrypto::dev_default() }`
- 生成随机 `tenant_id``operator_id`
- 复用现有 `make_state` 模式(见 `health_patient_tests.rs:15-21`
3. 添加访问方法:
- `db()``&DatabaseConnection`
- `health_state()``&HealthState`
- `tenant_id()``Uuid`
- `operator_id()``Uuid`
**验收标准**:
- `cargo test -p erp-server test_app_new` 通过
- TestApp Drop 时自动清理临时数据库(复用 TestDb 的 Drop 实现)
- 现有 7 个集成测试不受影响
---
### Task 2: TestFixture 工厂函数
**目标**: 提供预构建测试数据的工厂函数,减少每个测试的 setup 代码。
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/test_fixture.rs`
- 修改: `crates/erp-server/tests/integration.rs`(添加模块注册)
**详细步骤**:
1. 创建 `TestFixture` 模块,实现以下工厂函数:
```rust
impl TestApp {
/// 创建患者(含基本字段,可自定义覆盖)
pub async fn create_patient(&self, overrides: PatientOverrides) -> Patient { ... }
/// 创建医生
pub async fn create_doctor(&self, overrides: DoctorOverrides) -> DoctorProfile { ... }
/// 创建排班
pub async fn create_schedule(&self, doctor_id: Uuid, date: NaiveDate, ...) -> DoctorSchedule { ... }
/// 创建随访任务
pub async fn create_follow_up_task(&self, patient_id: Uuid, ...) -> FollowUpTask { ... }
/// 创建积分账户 + 初始积分
pub async fn create_points_account(&self, patient_id: Uuid, initial_balance: i32) -> PointsAccount { ... }
}
```
2. 使用 `Default` trait + 覆盖模式:
```rust
#[derive(Default)]
pub struct PatientOverrides {
pub name: Option<String>,
pub gender: Option<String>,
pub birth_date: Option<NaiveDate>,
pub id_number: Option<String>,
// ...
}
```
**验收标准**:
- 现有 `health_patient_tests.rs` 可用 fixture 重写(验证工厂函数可用)
- 每个工厂函数 ≤ 30 行代码
- 创建的数据包含正确的 `tenant_id``created_by`
---
### Task 3: 前端 MSW v2 初始配置
**涉及文件**:
- 新增: `apps/web/src/test/mocks/handlers.ts`
- 新增: `apps/web/src/test/mocks/server.ts`
- 修改: `apps/web/src/test/setup.ts`
- 修改: `apps/web/package.json`(添加 `msw` 依赖)
**详细步骤**:
1. 安装 MSW v2: `pnpm add -D msw`
2. 创建 `handlers.ts` — 初始仅包含 auth 基础 handler登录/刷新):
```typescript
export const handlers = [
http.post('/api/v1/auth/login', () => HttpResponse.json({ ... })),
http.post('/api/v1/auth/refresh', () => HttpResponse.json({ ... })),
]
```
3. 创建 `server.ts`:
```typescript
import { setupServer } from 'msw/node'
export const server = setupServer(...handlers)
```
4.`setup.ts` 中启动/清理 MSW server:
```typescript
beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
```
**验收标准**:
- `pnpm test` 无报错
- MSW server 正确拦截匹配的请求
- 未匹配的请求产生 warning不是 error
---
### Task 4: 前端覆盖率工具配置
**涉及文件**:
- 修改: `apps/web/package.json`
- 修改: `apps/web/vitest.config.ts`
**详细步骤**:
1. 安装: `pnpm add -D @vitest/coverage-v8`
2.`vitest.config.ts` 添加 coverage 配置:
```typescript
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
include: ['src/**/*.{ts,tsx}'],
exclude: ['src/test/**', 'src/**/*.test.*', 'src/**/*.d.ts'],
},
}
```
3. 添加 npm script: `"test:coverage": "vitest run --coverage"`
**验收标准**:
- `pnpm test:coverage` 生成覆盖率报告
- 输出包含各文件的行覆盖率百分比
---
### Task 5: 后端覆盖率工具安装验证
**涉及文件**: 无代码变更,仅环境安装
**详细步骤**:
1. 安装 `cargo-llvm-cov`: `cargo install cargo-llvm-cov`
2. 验证基本功能: `cargo llvm-cov --workspace --text`
3. 编写增量检查脚本 `scripts/coverage-diff-check.py`(或 shell
- 读取 `cargo llvm-cov --json` 输出
- 通过 `git diff --name-only origin/main` 获取变更文件列表
- 过滤出变更文件的覆盖率
- 低于 80% 的文件列表退出码 1
**验收标准**:
- `cargo llvm-cov --workspace --text` 输出覆盖率报告
- 增量检查脚本对模拟变更文件正确判断通过/不通过
---
### Task 6: CI workflow 初始创建
**涉及文件**:
- 新增: `.github/workflows/test.yml`
**详细步骤**:
1. 创建基础 CI workflow包含
- `backend-test` job: `cargo check``cargo test``cargo clippy`
- `frontend-test` job: `pnpm install``pnpm test:ci``pnpm build`
- PostgreSQL service 容器(仅 backend-test
- 环境变量配置(测试用 JWT secret、数据库 URL
2. 配置触发条件: `on: pull_request` + `on: push: main`
**验收标准**:
- Workflow 文件语法正确(`actionlint` 验证)
- 包含 spec 要求的完整验证链: check → test → clippy → build
---
## Phase 1: 高风险 Service 测试Week 1-2
> 共 6 个 service约 47 个测试。所有测试使用 TestApp + TestFixture 模式。
### Task 7: points_service 测试12 个)
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/health_points_tests.rs`
- 修改: `crates/erp-server/tests/integration.rs`
**测试用例清单**:
| 测试名 | 场景 |
|--------|------|
| `test_points_earn_sign_in` | 签到积分 — 首次签到成功,重复签到同天拒绝 |
| `test_points_earn_custom` | 自定义积分增加 |
| `test_points_consume_fifo_deduction` | FIFO 消费 — 先消耗最早的积分记录 |
| `test_points_consume_balance_insufficient` | 余额不足时返回错误 |
| `test_points_consume_exact_balance` | 消费金额等于余额 |
| `test_points_consume_partial` | 消费金额小于单条记录,正确拆分 |
| `test_points_account_create_on_first_earn` | 首次获取积分时自动创建账户 |
| `test_points_checkin_streak` | 连续签到奖励 |
| `test_points_order_create` | 积分兑换商品 — 创建订单 |
| `test_points_order_insufficient_cancel` | 积分不足时订单失败 |
| `test_points_transaction_history` | 交易记录查询 |
| `test_points_tenant_isolation` | 租户隔离 — A 的积分 B 看不到 |
**验收标准**:
- `cargo test -p erp-server health_points` 全部通过
- FIFO 消费逻辑有明确的事务性验证(回滚场景)
---
### Task 8: dialysis_service 测试8 个)
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/health_dialysis_tests.rs`
**测试用例清单**:
| 测试名 | 场景 |
|--------|------|
| `test_dialysis_create_basic` | 创建透析记录基本 CRUD |
| `test_dialysis_create_pii_encrypted` | PII 字段(病史、并发症)加密存储 |
| `test_dialysis_update_status_flow` | 状态流转: scheduled → in_progress → completed |
| `test_dialysis_list_by_patient` | 按患者 ID 过滤列表 |
| `test_dialysis_tenant_isolation` | 租户隔离 |
| `test_dialysis_version_conflict` | 乐观锁 — 旧版本更新拒绝 |
| `test_dialysis_soft_delete` | 软删除后不可见 |
| `test_dialysis_create_without_patient_returns_error` | 无效患者 ID 返回错误 |
**验收标准**:
- `cargo test -p erp-server health_dialysis` 全部通过
- PII 加密字段在数据库层面不可明文读取(验证密文格式)
---
### Task 9: alert_engine 测试8 个)
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/health_alert_tests.rs`(包含 engine + rule + service 测试)
**测试用例清单**:
| 测试名 | 场景 |
|--------|------|
| `test_alert_engine_evaluate_threshold_exceeded` | 体征超阈值触发告警 |
| `test_alert_engine_evaluate_threshold_normal` | 体征正常不触发 |
| `test_alert_engine_cooldown_prevents_duplicate` | cooldown 期内不重复触发 |
| `test_alert_engine_multiple_rules` | 多规则并行评估 |
| `test_alert_rule_crud` | 规则 CRUD 完整性 |
| `test_alert_rule_enable_disable` | 规则启用/禁用 |
| `test_alert_acknowledge` | 告警确认状态变更 |
| `test_alert_batch_acknowledge` | 批量确认 |
**验收标准**:
- `cargo test -p erp-server health_alert` 全部通过
- cooldown 逻辑精确到秒级验证
---
### Task 10: device_reading_service 测试8 个)
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/health_device_reading_tests.rs`
**测试用例清单**:
| 测试名 | 场景 |
|--------|------|
| `test_device_reading_batch_insert` | 批量插入(模拟 50 条数据) |
| `test_device_reading_upsert_hourly` | 降采样聚合 — 同一小时的数据合并 |
| `test_device_reading_query_range` | 时间范围查询 |
| `test_device_reading_query_by_device_type` | 按设备类型过滤 |
| `test_device_reading_tenant_isolation` | 租户隔离 |
| `test_device_reading_invalid_data_rejected` | 无效数据(空值、超范围)拒绝 |
| `test_device_reading_latest_by_patient` | 获取患者最新读数 |
| `test_device_reading_sync_event_published` | 数据同步后事件发布 |
**验收标准**:
- `cargo test -p erp-server health_device_reading` 全部通过
- 降采样逻辑的聚合值avg/min/max精度正确
---
### Task 11: 扩展已有患者测试(+3 个)
**涉及文件**:
- 修改: `crates/erp-server/tests/integration/health_patient_tests.rs`
**新增测试**:
| 测试名 | 场景 |
|--------|------|
| `test_patient_update_version_conflict` | 乐观锁冲突 |
| `test_patient_create_with_pii_encrypted` | PII 字段加密存储验证 |
| `test_patient_search_by_name` | 按名称搜索 |
---
### Task 12: 扩展已有预约测试(+3 个)
**涉及文件**:
- 修改: `crates/erp-server/tests/integration/health_appointment_tests.rs`
**新增测试**:
| 测试名 | 场景 |
|--------|------|
| `test_appointment_cas_exceeds_max_returns_error` | 预约超额拒绝 |
| `test_appointment_cancel_releases_slot` | 取消预约释放名额 |
| `test_appointment_status_flow` | 状态完整流转 |
**Phase 1 验收**:
- 所有新增测试通过: `cargo test -p erp-server`
- 覆盖率提升约 20-25%(从当前基线)
---
## Phase 2: 中风险 Service 测试Week 3-4
> 共 5 个 service + erp-ai约 47 个测试。
### Task 13: patient_service 完整测试10 个)
**涉及文件**:
- 修改: `crates/erp-server/tests/integration/health_patient_tests.rs`
**新增测试**: 家庭成员管理 CRUD4、标签管理 CRUD3、健康摘要2、搜索/过滤1
### Task 14: appointment_service 完整测试8 个)
**涉及文件**:
- 修改: `crates/erp-server/tests/integration/health_appointment_tests.rs`
**新增测试**: 排班管理 CRUD3、预约时间冲突1、并发安全2、多租户隔离2
### Task 15: follow_up_service 测试8 个)
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/health_follow_up_tests.rs`
**新增测试**: 状态机 6 种转换6、任务分配1、执行记录1
### Task 16: consultation_service 测试5 个)
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/health_consultation_tests.rs`
**新增测试**: 会话 CRUD3、消息收发1、状态变更1
### Task 17: doctor_service 测试4 个)
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/health_doctor_tests.rs`
**新增测试**: CRUD3、排班关联1
### Task 18: erp-ai 基础测试12 个)
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/ai_prompt_template_tests.rs`
- 新增: `crates/erp-server/tests/integration/ai_analysis_tests.rs`
- 新增: `crates/erp-server/tests/integration/ai_usage_tests.rs`
**新增测试**: 提示模板 CRUD4、分析记录 CRUD4、使用统计4
**Phase 2 验收**:
- `cargo test -p erp-server` 全部通过
- 后端 service 层覆盖率 ≥ 55%
---
## Phase 3: 低风险 Service + Handler + DTOWeek 5-6
> 共 14 个测试文件,约 71 个测试。
### Task 19-28: 低风险 Service 测试
每个 service 一个测试文件遵循统一模式CRUD + 租户隔离 + 软删除 + 乐观锁):
| Task | Service | 文件 | 测试数 |
|------|---------|------|--------|
| 19 | article_service | health_article_tests.rs | 5 |
| 20 | article_category_service | (合并入 Task 19 | 4 |
| 21 | article_tag_service | (合并入 Task 19 | 3 |
| 22 | offline_event_service | health_offline_event_tests.rs | 4 |
| 23 | consent_service | health_consent_tests.rs | 3 |
| 24 | diagnosis_service | health_diagnosis_tests.rs | 3 |
| 25 | daily_monitoring_service | health_daily_monitoring_tests.rs | 4 |
| 26 | critical_value_threshold_service | (合并入 Task 9 alert 测试文件) | 3 |
| 27 | stats_service | health_stats_tests.rs | 5 |
| 28 | health_data_service | health_data_tests.rs | 8 |
### Task 29: trend_service 集成测试补充3 个)
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/health_trend_tests.rs`
### Task 30: Handler 层 HTTP 测试16 个)
**目标**: 验证 HTTP 层的权限检查、请求解析、响应格式。
**涉及文件**:
- 新增: `crates/erp-server/tests/integration/health_handler_tests.rs`
**测试模式**: 使用 `tower::ServiceExt` 调用 Axum Router验证 HTTP 状态码和响应体格式。
**覆盖**: 每个核心 handler 1 个测试(患者/预约/随访/咨询/医生/文章/积分/体征/告警/透析等 16 个 handler
### Task 31: DTO 转换测试10 个)
**涉及文件**:
- 新增: `crates/erp-health/src/dto/dto_tests.rs`(内联 `#[cfg(test)]`
**测试内容**: 请求 DTO 反序列化JSON → struct+ 响应 DTO 序列化struct → JSON+ 边界值
### Task 32: CI 后端增量门禁上线
**涉及文件**:
- 修改: `.github/workflows/test.yml`
**操作**: 在 backend-test job 中添加增量覆盖率检查步骤
**Phase 3 验收**:
- 后端全量测试覆盖率 ≥ 70%
- CI 增量门禁生效
---
## Phase 4: 前端补测Week 7-9
### Task 33: API 层测试 — client.ts12 个)
**涉及文件**:
- 新增: `apps/web/src/api/client.test.ts`
**测试内容**: token 主动刷新、401 被动刷新、并发请求队列去重、GET 缓存命中/过期、错误映射
**工具**: MSW mock `/api/v1/auth/refresh` 等端点
### Task 34: API 层测试 — health 模块19 个)
**涉及文件**:
- 新增: `apps/web/src/api/health/patients.test.ts`
- 新增: `apps/web/src/api/health/appointments.test.ts`
- 新增: `apps/web/src/api/health/other.test.ts`
**测试内容**: 每个 API 文件的 CRUD 参数正确性、分页参数、错误映射
### Task 35: Store 层测试17 个)
**涉及文件**:
- 新增: `apps/web/src/stores/auth.test.ts`
- 新增: `apps/web/src/stores/plugin.test.ts`
- 新增: `apps/web/src/stores/health.test.ts`
- 新增: `apps/web/src/stores/message.test.ts`
### Task 36: Hooks 测试12 个)
**涉及文件**:
- 新增: `apps/web/src/hooks/useApiRequest.test.ts`
- 新增: `apps/web/src/hooks/usePaginatedData.test.ts`
- 新增: `apps/web/src/hooks/usePermission.test.ts`
### Task 37: 页面组件测试19 个)
**涉及文件**:
- 新增 8 个测试文件,对应 8 个核心健康模块页面
### Task 38: Playwright E2E — 患者管理1 spec
**涉及文件**:
- 新增: `apps/web/e2e/health-patient.spec.ts`
### Task 39: Playwright E2E — 预约/随访/咨询3 spec
**涉及文件**:
- 新增: `apps/web/e2e/health-appointment.spec.ts`
- 新增: `apps/web/e2e/health-follow-up.spec.ts`
- 新增: `apps/web/e2e/health-consultation.spec.ts`
### Task 40: Playwright E2E — 统计仪表板1 spec
**涉及文件**:
- 新增: `apps/web/e2e/health-statistics.spec.ts`
**Phase 4 验收**:
- 前端全量覆盖率 ≥ 60%
- 健康模块 E2E ≥ 5 个 spec
---
## 验证与 CI 门禁
### Task 41: 后端增量门禁上线Week 6 后)
**操作**: 在 CI workflow 中启用后端增量覆盖率检查,新增/修改文件覆盖率 < 80% 时 PR 不允许合并。
### Task 42: 前端增量门禁上线Week 9 后)
**操作**: 同上,启用前端增量覆盖率检查。
### Task 43: 全量覆盖率验证 + 复盘Week 12 后)
**操作**:
1. 运行 `cargo llvm-cov --workspace --text` 记录后端全量覆盖率
2. 运行 `pnpm test:coverage` 记录前端全量覆盖率
3. 对比 spec 目标(后端 80% + 前端 60%),分析差距
4. 输出复盘文档到 `docs/discussions/2026-05-XX-test-coverage-retrospective.md`
5. 制定后续补测计划(如有必要)
---
## 执行原则
1. **每 Task 完成后立即提交** — 不积压,保持可追溯
2. **先验证基础设施** — Phase 0 的 TestApp/TestFixture 必须先通过才能开始 Phase 1
3. **测试文件独立于业务代码** — 新增测试文件不修改已有业务逻辑
4. **cargo check/test 必须通过** — 每个 Task 完成后运行 `cargo test --workspace` 验证
5. **Phase 间复盘** — 每个 Phase 结束后统计覆盖率,与目标对比,必要时调整