docs: 测试覆盖率提升实施计划
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

基于 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 个测试用例
This commit is contained in:
iven
2026-04-27 00:25:30 +08:00
parent 5b81a0051f
commit 2defbd7ab3

View File

@@ -0,0 +1,544 @@
# 测试覆盖率提升实施计划
> 设计规格: `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 结束后统计覆盖率,与目标对比,必要时调整