# 测试覆盖率提升实施计划 > 设计规格: `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, pub gender: Option, pub birth_date: Option, pub id_number: Option, // ... } ``` **验收标准**: - 现有 `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` **新增测试**: 家庭成员管理 CRUD(4)、标签管理 CRUD(3)、健康摘要(2)、搜索/过滤(1) ### Task 14: appointment_service 完整测试(8 个) **涉及文件**: - 修改: `crates/erp-server/tests/integration/health_appointment_tests.rs` **新增测试**: 排班管理 CRUD(3)、预约时间冲突(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` **新增测试**: 会话 CRUD(3)、消息收发(1)、状态变更(1) ### Task 17: doctor_service 测试(4 个) **涉及文件**: - 新增: `crates/erp-server/tests/integration/health_doctor_tests.rs` **新增测试**: CRUD(3)、排班关联(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` **新增测试**: 提示模板 CRUD(4)、分析记录 CRUD(4)、使用统计(4) **Phase 2 验收**: - `cargo test -p erp-server` 全部通过 - 后端 service 层覆盖率 ≥ 55% --- ## Phase 3: 低风险 Service + Handler + DTO(Week 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.ts(12 个) **涉及文件**: - 新增: `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 结束后统计覆盖率,与目标对比,必要时调整