基于 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 个测试用例
18 KiB
测试覆盖率提升实施计划
设计规格:
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
详细步骤:
- 在
test_db.rs中新增TestAppstruct:
pub struct TestApp {
test_db: TestDb,
health_state: HealthState,
tenant_id: Uuid,
operator_id: Uuid,
}
-
实现
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)
- 调用
-
添加访问方法:
db()→&DatabaseConnectionhealth_state()→&HealthStatetenant_id()→Uuidoperator_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(添加模块注册)
详细步骤:
- 创建
TestFixture模块,实现以下工厂函数:
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 { ... }
}
- 使用
Defaulttrait + 覆盖模式:
#[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依赖)
详细步骤:
- 安装 MSW v2:
pnpm add -D msw - 创建
handlers.ts— 初始仅包含 auth 基础 handler(登录/刷新):
export const handlers = [
http.post('/api/v1/auth/login', () => HttpResponse.json({ ... })),
http.post('/api/v1/auth/refresh', () => HttpResponse.json({ ... })),
]
- 创建
server.ts:
import { setupServer } from 'msw/node'
export const server = setupServer(...handlers)
- 在
setup.ts中启动/清理 MSW server:
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
详细步骤:
- 安装:
pnpm add -D @vitest/coverage-v8 - 在
vitest.config.ts添加 coverage 配置:
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
include: ['src/**/*.{ts,tsx}'],
exclude: ['src/test/**', 'src/**/*.test.*', 'src/**/*.d.ts'],
},
}
- 添加 npm script:
"test:coverage": "vitest run --coverage"
验收标准:
pnpm test:coverage生成覆盖率报告- 输出包含各文件的行覆盖率百分比
Task 5: 后端覆盖率工具安装验证
涉及文件: 无代码变更,仅环境安装
详细步骤:
- 安装
cargo-llvm-cov:cargo install cargo-llvm-cov - 验证基本功能:
cargo llvm-cov --workspace --text - 编写增量检查脚本
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
详细步骤:
-
创建基础 CI workflow,包含:
backend-testjob:cargo check→cargo test→cargo clippyfrontend-testjob:pnpm install→pnpm test:ci→pnpm build- PostgreSQL service 容器(仅 backend-test)
- 环境变量配置(测试用 JWT secret、数据库 URL)
-
配置触发条件:
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 后)
操作:
- 运行
cargo llvm-cov --workspace --text记录后端全量覆盖率 - 运行
pnpm test:coverage记录前端全量覆盖率 - 对比 spec 目标(后端 80% + 前端 60%),分析差距
- 输出复盘文档到
docs/discussions/2026-05-XX-test-coverage-retrospective.md - 制定后续补测计划(如有必要)
执行原则
- 每 Task 完成后立即提交 — 不积压,保持可追溯
- 先验证基础设施 — Phase 0 的 TestApp/TestFixture 必须先通过才能开始 Phase 1
- 测试文件独立于业务代码 — 新增测试文件不修改已有业务逻辑
- cargo check/test 必须通过 — 每个 Task 完成后运行
cargo test --workspace验证 - Phase 间复盘 — 每个 Phase 结束后统计覆盖率,与目标对比,必要时调整