1
0
Files
nex/openspec/specs/test-coverage/spec.md
lanyuanxiaoyao 4e86adffb7 feat: 系统性改进后端测试体系
- 新增 6 个测试场景 (config load pipe, handler errors, service aggregation, engine degradation, openai decoder edges, negative tests)
- 更新测试工具规格 (mockgen, in-memory SQLite)
- 覆盖率目标从 >80% 提升至 >85%
- 新增 test-unit 和 test-integration Makefile 命令
- 新增死代码清理和 mockgen 需求
- 归档变更至 openspec/changes/archive/2026-04-22-improve-backend-testing/
2026-04-22 13:18:51 +08:00

264 lines
10 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.
# Test Coverage
## Purpose
定义测试覆盖规范,建立后端和前端的单元测试、集成测试及 E2E 测试体系,确保核心业务逻辑的测试覆盖率达到目标水平。
## Requirements
### Requirement: 建立单元测试体系
系统 SHALL 建立完整的单元测试体系,覆盖核心业务逻辑。
#### Scenario: config 包测试覆盖
- **WHEN** 运行 config 包的单元测试
- **THEN** SHALL 覆盖 Provider、Model、Stats 的 CRUD 操作
- **THEN** SHALL 测试正常场景和错误场景
- **THEN** SHALL 验证数据库操作的准确性
#### Scenario: router 包测试覆盖
- **WHEN** 运行 router 包的单元测试
- **THEN** SHALL 覆盖模型路由逻辑
- **THEN** SHALL 测试模型不存在、模型禁用、供应商禁用等场景
- **THEN** SHALL 验证路由结果的正确性
#### Scenario: protocol 包测试覆盖
- **WHEN** 运行 protocol 包的单元测试
- **THEN** SHALL 覆盖 OpenAI 和 Anthropic 协议转换逻辑
- **THEN** SHALL 测试请求转换、响应转换、流式转换
- **THEN** SHALL 验证转换的准确性和完整性
#### Scenario: config 加载管道集成测试
- **WHEN** 运行 config 加载管道的集成测试
- **THEN** SHALL 验证 LoadConfigFromPath 正确加载默认值
- **THEN** SHALL 验证环境变量(`NEX_` 前缀)覆盖默认值
- **THEN** SHALL 验证 YAML 配置文件正确读取
- **THEN** SHALL 验证优先级链CLI 参数 > 环境变量 > YAML 文件 > 默认值
- **THEN** SHALL 验证首次启动自动创建配置文件
- **THEN** SHALL 验证 SaveConfig 后重新 LoadConfig 数据一致
#### Scenario: handler 错误分支测试
- **WHEN** 运行 handler 层的单元测试
- **THEN** SHALL 覆盖 ModelHandler.CreateModel 的所有错误分支ErrProviderNotFound(400)、ErrDuplicateModel(409)、通用错误(500)
- **THEN** SHALL 覆盖 ProviderHandler.UpdateProvider 的所有错误分支ErrRecordNotFound(404)、ErrImmutableField(400)、通用错误(500)
- **THEN** SHALL 验证每个错误分支返回正确的 HTTP 状态码和错误响应格式
#### Scenario: service 聚合逻辑测试
- **WHEN** 运行 service 层的单元测试
- **THEN** SHALL 覆盖 StatsService.Aggregate 的所有分组模式byProvider、byModel、byDate
- **THEN** SHALL 验证 aggregateByModel 正确拼接和拆分 providerID/modelName key
- **THEN** SHALL 验证 aggregateByDate 正确格式化日期并聚合
- **THEN** SHALL 覆盖空结果集、同名模型不同 provider 等边界场景
#### Scenario: provider service 工具方法测试
- **WHEN** 运行 provider service 的单元测试
- **THEN** SHALL 验证 isUniqueConstraintError 正确识别 SQLite 唯一约束冲突错误消息
- **THEN** SHALL 验证 List 方法对每个 provider 调用 MaskAPIKey
#### Scenario: engine 降级路径测试
- **WHEN** 运行 conversion engine 的单元测试
- **THEN** SHALL 验证 convertEmbeddingBody 在 decode 失败时返回原始 bodypassthrough
- **THEN** SHALL 验证 convertRerankBody 在 decode 失败时返回原始 bodypassthrough
- **THEN** SHALL 验证降级过程不 panic、不返回空 body
#### Scenario: openai decoder 边界场景测试
- **WHEN** 运行 openai decoder 的单元测试
- **THEN** SHALL 覆盖 assistant message content 为 JSON 数组格式的解析text/refusal 类型)
- **THEN** SHALL 验证 decodeContentParts 正确提取文本内容和拒绝消息
#### Scenario: 业务逻辑负面测试
- **WHEN** 运行业务逻辑负面测试
- **THEN** SHALL 覆盖 JSON 格式错误请求体的处理
- **THEN** SHALL 覆盖并发创建相同 provider + model 的重复检测
- **THEN** SHALL 覆盖空结果集查询的正确处理
### Requirement: 建立集成测试体系
系统 SHALL 建立集成测试体系,覆盖 API 端到端流程。
#### Scenario: OpenAI 协议集成测试
- **WHEN** 运行 OpenAI 协议的集成测试
- **THEN** SHALL 测试完整的请求-响应流程
- **THEN** SHALL 测试流式响应流程
- **THEN** SHALL 测试错误处理流程
#### Scenario: Anthropic 协议集成测试
- **WHEN** 运行 Anthropic 协议的集成测试
- **THEN** SHALL 测试完整的请求-响应流程
- **THEN** SHALL 测试流式响应流程
- **THEN** SHALL 测试协议转换的准确性
#### Scenario: 管理接口集成测试
- **WHEN** 运行管理接口的集成测试
- **THEN** SHALL 测试 Provider、Model、Stats 的 CRUD 操作
- **THEN** SHALL 验证 API 响应格式
- **THEN** SHALL 测试错误场景
### Requirement: 提供测试工具函数
系统 SHALL 提供测试工具函数,简化测试编写。
#### Scenario: 测试数据库初始化
- **WHEN** 编写需要数据库的测试
- **THEN** SHALL 提供统一的测试数据库初始化函数 `SetupTestDB`
- **THEN** SHALL 统一使用 SQLite `:memory:` + `MaxOpenConns(1)` 策略
- **THEN** SHALL 在测试结束后自动清理
- **THEN** 所有测试包 SHALL 通过 `tests.SetupTestDB()` 获取测试数据库,不允许各自独立实现
#### Scenario: Mock 工具
- **WHEN** 编写需要 Mock 的测试
- **THEN** SHALL 使用 mockgen 自动生成 mock 实现
- **THEN** SHALL 在接口定义文件中使用 `//go:generate` 注解标注生成命令
- **THEN** 生成的 mock SHALL 放置在 `tests/mocks/` 目录下
- **THEN** SHALL 覆盖 service 和 repository 接口的 mock 生成
### Requirement: 达到测试覆盖率目标
系统 SHALL 达到 > 85% 的测试覆盖率。
#### Scenario: 总体覆盖率
- **WHEN** 运行所有测试并生成覆盖率报告
- **THEN** 总体覆盖率 SHALL 大于 85%
- **THEN** 核心包config、service、handler、conversion、repository覆盖率 SHALL 大于 85%
#### Scenario: 覆盖率报告生成
- **WHEN** 运行测试覆盖率命令
- **THEN** SHALL 生成覆盖率报告文件
- **THEN** SHALL 支持生成 HTML 格式报告
- **THEN** SHALL 显示每个文件的覆盖率
### Requirement: 集成到构建流程
测试 SHALL 集成到构建流程中。
#### Scenario: 运行测试命令
- **WHEN** 执行 `make test` 命令
- **THEN** SHALL 运行所有单元测试和集成测试
- **THEN** SHALL 显示测试结果
- **THEN** SHALL 在测试失败时返回非零退出码
#### Scenario: 分类测试命令
- **WHEN** 执行 `make test-unit` 命令
- **THEN** SHALL 仅运行 `./internal/...``./pkg/...` 下的单元测试
- **WHEN** 执行 `make test-integration` 命令
- **THEN** SHALL 仅运行 `./tests/...` 下的集成测试
#### Scenario: 覆盖率检查命令
- **WHEN** 执行 `make test-coverage` 命令
- **THEN** SHALL 运行测试并生成覆盖率报告
- **THEN** SHALL 检查覆盖率是否达标
- **THEN** SHALL 在覆盖率不足时返回非零退出码
### Requirement: 建立前端单元测试覆盖
前端代码 SHALL 建立单元测试覆盖,纳入整体测试覆盖率统计。
#### Scenario: API 层测试覆盖
- **WHEN** 运行前端 API 层的单元测试
- **THEN** SHALL 覆盖 api/client.ts 的请求封装和字段转换逻辑
- **THEN** SHALL 覆盖 api/providers.ts、api/models.ts、api/stats.ts 的所有函数
- **THEN** SHALL 使用 MSW mock API 响应
#### Scenario: Hooks 测试覆盖
- **WHEN** 运行前端 Hooks 的单元测试
- **THEN** SHALL 覆盖 useProviders、useModels、useStats 的查询和变更逻辑
- **THEN** SHALL 验证缓存失效和自动刷新行为
### Requirement: 建立前端组件测试覆盖
前端 SHALL 使用 React Testing Library 建立组件测试覆盖。
#### Scenario: 页面组件测试覆盖
- **WHEN** 运行前端组件测试
- **THEN** SHALL 覆盖 ProviderTable 的列表渲染和交互操作
- **THEN** SHALL 覆盖 ProviderForm 的表单校验和提交
- **THEN** SHALL 覆盖 ModelForm 的表单校验和提交
- **THEN** SHALL 覆盖 StatsTable 的筛选和数据展示
### Requirement: 建立前端 E2E 测试覆盖
前端 SHALL 使用 Playwright 建立 E2E 测试覆盖。
#### Scenario: 供应商管理 E2E 覆盖
- **WHEN** 运行 E2E 测试
- **THEN** SHALL 覆盖供应商创建、编辑、删除的完整用户流程
- **THEN** SHALL 覆盖模型创建、编辑、删除的完整用户流程
#### Scenario: 统计查询 E2E 覆盖
- **WHEN** 运行 E2E 测试
- **THEN** SHALL 覆盖统计页面的加载和筛选查询流程
- **THEN** SHALL 覆盖页面间的导航切换
### Requirement: 前端测试集成到构建流程
前端测试 SHALL 集成到项目的构建和验证流程中。
#### Scenario: 运行前端测试命令
- **WHEN** 在 frontend/ 目录执行测试命令
- **THEN** SHALL 运行所有 Vitest 单元测试和组件测试
- **THEN** SHALL 显示测试结果
- **THEN** SHALL 在测试失败时返回非零退出码
#### Scenario: 运行前端 E2E 测试命令
- **WHEN** 在 frontend/ 目录执行 E2E 测试命令
- **THEN** SHALL 启动 Playwright 运行 E2E 测试
- **THEN** SHALL 在测试失败时返回非零退出码
### Requirement: 清理 ProviderRepository 死代码
系统 SHALL 移除 ProviderRepository 中未被调用的重复方法。
#### Scenario: 移除死代码方法
- **WHEN** 审查 ProviderRepository 接口
- **THEN** SHALL 移除 `ListEnabledModels()` 方法声明和实现
- **THEN** SHALL 移除 `FindByProviderAndModelName()` 方法声明和实现
- **THEN** SHALL 确保所有现有调用者通过 ModelRepository 访问等效功能
- **THEN** SHALL 不影响任何运行时行为
### Requirement: 使用 mockgen 生成 mock
系统 SHALL 使用 mockgen 为接口自动生成 mock 实现,替代手写 mock。
#### Scenario: mock 生成配置
- **WHEN** 在接口定义文件中添加 `//go:generate mockgen` 注解
- **THEN** SHALL 为 `ProviderService``ModelService``RoutingService``StatsService` 接口生成 mock
- **THEN** SHALL 为 `ModelRepository``ProviderRepository``StatsRepository` 接口生成 mock
- **THEN** SHALL 为 `ProviderClient` 接口生成 mock
- **THEN** 生成的 mock SHALL 输出到 `tests/mocks/` 目录
#### Scenario: 替换手写 mock
- **WHEN** mockgen 生成的 mock 就绪
- **THEN** handler 测试中的手写 mock SHALL 被替换为生成的 mock
- **THEN** 所有测试 SHALL 继续通过,行为不变