384 lines
16 KiB
Markdown
384 lines
16 KiB
Markdown
# 测试最佳实践
|
||
|
||
<cite>
|
||
**本文档引用的文件**
|
||
- [tests/conftest.py](file://tests/conftest.py)
|
||
- [tests/test_auth.py](file://tests/test_auth.py)
|
||
- [tests/test_citations.py](file://tests/test_citations.py)
|
||
- [tests/test_queries.py](file://tests/test_queries.py)
|
||
- [tests/test_citation_engine.py](file://tests/test_citation_engine.py)
|
||
- [backend/requirements.txt](file://backend/requirements.txt)
|
||
- [docker-compose.yml](file://docker-compose.yml)
|
||
- [backend/app/main.py](file://backend/app/main.py)
|
||
- [backend/app/api/deps.py](file://backend/app/api/deps.py)
|
||
- [backend/app/workers/citation_engine.py](file://backend/app/workers/citation_engine.py)
|
||
- [backend/app/workers/scheduler.py](file://backend/app/workers/scheduler.py)
|
||
- [backend/alembic.ini](file://backend/alembic.ini)
|
||
</cite>
|
||
|
||
## 目录
|
||
1. [简介](#简介)
|
||
2. [项目结构](#项目结构)
|
||
3. [核心组件](#核心组件)
|
||
4. [架构总览](#架构总览)
|
||
5. [详细组件分析](#详细组件分析)
|
||
6. [依赖分析](#依赖分析)
|
||
7. [性能考虑](#性能考虑)
|
||
8. [故障排查指南](#故障排查指南)
|
||
9. [结论](#结论)
|
||
10. [附录](#附录)
|
||
|
||
## 简介
|
||
本指南面向GEO项目的测试工作,系统性地给出测试编写规范、覆盖率要求与测量方法、持续集成配置建议、调试技巧以及测试环境与数据管理的最佳实践。文档以仓库现有测试代码为基础,结合后端FastAPI应用、数据库与任务调度器的实际实现,帮助团队建立一致、可维护、高覆盖度的测试体系。
|
||
|
||
## 项目结构
|
||
测试相关的核心位置与职责如下:
|
||
- tests 目录:集中存放pytest测试用例与通用fixture配置
|
||
- backend/app:后端应用入口、API路由、依赖注入、模型与服务
|
||
- backend/app/workers:引用检测引擎与任务调度器
|
||
- docker-compose.yml:本地开发与测试所需的数据库与缓存服务编排
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "测试层"
|
||
T1["tests/conftest.py"]
|
||
T2["tests/test_auth.py"]
|
||
T3["tests/test_citations.py"]
|
||
T4["tests/test_queries.py"]
|
||
T5["tests/test_citation_engine.py"]
|
||
end
|
||
subgraph "后端应用"
|
||
A1["app/main.py"]
|
||
A2["app/api/deps.py"]
|
||
W1["workers/citation_engine.py"]
|
||
W2["workers/scheduler.py"]
|
||
end
|
||
subgraph "基础设施"
|
||
D1["docker-compose.yml"]
|
||
DB["PostgreSQL"]
|
||
R["Redis"]
|
||
end
|
||
T1 --> A1
|
||
T2 --> A1
|
||
T3 --> A1
|
||
T4 --> A1
|
||
T5 --> W1
|
||
A1 --> W2
|
||
W1 --> DB
|
||
W2 --> DB
|
||
D1 --> DB
|
||
D1 --> R
|
||
```
|
||
|
||
图表来源
|
||
- [tests/conftest.py:1-71](file://tests/conftest.py#L1-L71)
|
||
- [tests/test_auth.py:1-104](file://tests/test_auth.py#L1-L104)
|
||
- [tests/test_citations.py:1-93](file://tests/test_citations.py#L1-L93)
|
||
- [tests/test_queries.py:1-154](file://tests/test_queries.py#L1-L154)
|
||
- [tests/test_citation_engine.py:1-54](file://tests/test_citation_engine.py#L1-L54)
|
||
- [backend/app/main.py:1-48](file://backend/app/main.py#L1-L48)
|
||
- [backend/app/api/deps.py:1-43](file://backend/app/api/deps.py#L1-L43)
|
||
- [backend/app/workers/citation_engine.py:1-309](file://backend/app/workers/citation_engine.py#L1-L309)
|
||
- [backend/app/workers/scheduler.py:1-95](file://backend/app/workers/scheduler.py#L1-L95)
|
||
- [docker-compose.yml:1-71](file://docker-compose.yml#L1-L71)
|
||
|
||
章节来源
|
||
- [tests/conftest.py:1-71](file://tests/conftest.py#L1-L71)
|
||
- [docker-compose.yml:1-71](file://docker-compose.yml#L1-L71)
|
||
|
||
## 核心组件
|
||
- 测试夹具与环境
|
||
- 会话级mock:屏蔽真实任务调度器,避免后台作业影响测试稳定性
|
||
- 认证夹具:生成模拟用户、JWT令牌与请求头,统一处理鉴权依赖
|
||
- 异步HTTP客户端:基于ASGI传输,便于对FastAPI路由进行端到端测试
|
||
- API测试范围
|
||
- 认证模块:注册、登录、个人信息读取
|
||
- 查询模块:创建、列表、更新、删除、详情与权限边界
|
||
- 引用数据模块:查询、统计、导出CSV
|
||
- 单元测试范围
|
||
- 引用检测引擎:品牌匹配、竞争品牌识别、引用位置与置信度
|
||
- 测试依赖
|
||
- pytest、pytest-asyncio、httpx、aiosqlite、unittest.mock
|
||
|
||
章节来源
|
||
- [tests/conftest.py:19-71](file://tests/conftest.py#L19-L71)
|
||
- [tests/test_auth.py:25-104](file://tests/test_auth.py#L25-L104)
|
||
- [tests/test_queries.py:29-154](file://tests/test_queries.py#L29-L154)
|
||
- [tests/test_citations.py:23-93](file://tests/test_citations.py#L23-L93)
|
||
- [tests/test_citation_engine.py:6-54](file://tests/test_citation_engine.py#L6-L54)
|
||
- [backend/requirements.txt:31-35](file://backend/requirements.txt#L31-L35)
|
||
|
||
## 架构总览
|
||
下图展示测试执行路径与关键依赖交互,体现测试如何通过夹具注入、路由访问与服务mock实现可控的测试场景。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Py as "pytest"
|
||
participant Fix as "conftest夹具"
|
||
participant AC as "AsyncClient"
|
||
participant APP as "FastAPI应用"
|
||
participant DEP as "依赖注入(get_current_user)"
|
||
participant SVC as "业务服务(被patch)"
|
||
participant DB as "数据库/缓存"
|
||
Py->>Fix : "加载会话级fixture"
|
||
Fix->>APP : "注入依赖覆盖(get_current_user)"
|
||
Py->>AC : "创建异步HTTP客户端"
|
||
AC->>APP : "发送API请求"
|
||
APP->>DEP : "解析JWT并解析当前用户"
|
||
DEP-->>APP : "返回模拟用户"
|
||
APP->>SVC : "调用业务逻辑(被patch)"
|
||
SVC-->>APP : "返回测试数据/异常"
|
||
APP-->>AC : "响应JSON/状态码"
|
||
AC-->>Py : "断言结果"
|
||
APP->>DB : "读写(测试中通常隔离)"
|
||
```
|
||
|
||
图表来源
|
||
- [tests/conftest.py:19-71](file://tests/conftest.py#L19-L71)
|
||
- [tests/test_auth.py:25-104](file://tests/test_auth.py#L25-L104)
|
||
- [tests/test_queries.py:29-154](file://tests/test_queries.py#L29-L154)
|
||
- [tests/test_citations.py:23-93](file://tests/test_citations.py#L23-L93)
|
||
- [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43)
|
||
- [backend/app/main.py:13-48](file://backend/app/main.py#L13-L48)
|
||
|
||
## 详细组件分析
|
||
|
||
### 测试命名约定与结构
|
||
- 文件命名
|
||
- 使用 test_xxx.py,如 test_auth.py、test_queries.py、test_citations.py、test_citation_engine.py
|
||
- 函数命名
|
||
- 使用 test_xxx_pattern,如 test_register_success、test_create_query_exceeds_limit
|
||
- 夹具命名
|
||
- 使用 fixture_xxx 或 mock_xxx,如 mock_user、auth_headers、override_get_current_user
|
||
- 断言风格
|
||
- 明确断言状态码、响应体字段与业务语义,如“包含特定错误信息”“返回CSV头”
|
||
- 注释标准
|
||
- 每个fixture与关键测试函数添加简要说明,解释用途与边界条件
|
||
|
||
章节来源
|
||
- [tests/test_auth.py:11-22](file://tests/test_auth.py#L11-L22)
|
||
- [tests/test_queries.py:10-26](file://tests/test_queries.py#L10-L26)
|
||
- [tests/test_citations.py:8-20](file://tests/test_citations.py#L8-L20)
|
||
- [tests/test_citation_engine.py:3-5](file://tests/test_citation_engine.py#L3-L5)
|
||
|
||
### 认证与授权测试
|
||
- 关键点
|
||
- 使用依赖覆盖模拟当前用户,确保受保护路由在已认证与未认证两种场景下的行为
|
||
- 通过patch替换服务层方法,验证成功与失败路径
|
||
- 推荐断言
|
||
- 成功:200/201响应、返回必要字段、token类型与用户信息
|
||
- 失败:4xx状态码、错误消息包含预期关键字
|
||
- 安全性
|
||
- 确保未覆盖依赖时,未认证访问返回401
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant C as "客户端"
|
||
participant A as "认证路由"
|
||
participant D as "get_current_user"
|
||
participant S as "认证服务(被patch)"
|
||
C->>A : "POST /api/v1/auth/login"
|
||
A->>D : "解析JWT并解析用户"
|
||
D-->>A : "返回模拟用户"
|
||
A->>S : "authenticate_user(...)"
|
||
S-->>A : "返回用户或None"
|
||
A-->>C : "200/401 + JSON"
|
||
```
|
||
|
||
图表来源
|
||
- [tests/test_auth.py:62-84](file://tests/test_auth.py#L62-L84)
|
||
- [tests/conftest.py:42-50](file://tests/conftest.py#L42-L50)
|
||
- [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43)
|
||
|
||
章节来源
|
||
- [tests/test_auth.py:25-104](file://tests/test_auth.py#L25-L104)
|
||
- [tests/conftest.py:28-62](file://tests/conftest.py#L28-L62)
|
||
- [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43)
|
||
|
||
### 查询管理测试
|
||
- 关键点
|
||
- 使用mock对象构造典型查询实体,覆盖创建、列表、更新、删除、不存在与越权访问
|
||
- 通过patch触发权限限制与业务异常,验证403/404响应
|
||
- 推荐断言
|
||
- 字段一致性、分页总数、状态转换、权限边界
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Start(["开始: 发送请求"]) --> Patch["patch 业务方法"]
|
||
Patch --> CallAPI["调用 /queries/* 路由"]
|
||
CallAPI --> Resp{"响应状态码"}
|
||
Resp --> |201/200| AssertOK["断言返回字段与业务语义"]
|
||
Resp --> |403/404| AssertErr["断言错误消息与权限边界"]
|
||
AssertOK --> End(["结束"])
|
||
AssertErr --> End
|
||
```
|
||
|
||
图表来源
|
||
- [tests/test_queries.py:30-70](file://tests/test_queries.py#L30-L70)
|
||
- [tests/test_queries.py:127-139](file://tests/test_queries.py#L127-L139)
|
||
|
||
章节来源
|
||
- [tests/test_queries.py:29-154](file://tests/test_queries.py#L29-L154)
|
||
|
||
### 引用数据与报告测试
|
||
- 关键点
|
||
- mock服务返回结构化数据,验证列表、统计聚合与CSV导出的响应头与内容
|
||
- 推荐断言
|
||
- 统计字段存在性与合理性、CSV内容片段包含关键字段
|
||
|
||
章节来源
|
||
- [tests/test_citations.py:23-93](file://tests/test_citations.py#L23-L93)
|
||
|
||
### 引用检测引擎单元测试
|
||
- 关键点
|
||
- 测试品牌匹配器的精确、别名、模糊匹配与无匹配场景,以及引用位置与置信度
|
||
- 测试竞争品牌检测器对不同行业类别的识别
|
||
- 推荐断言
|
||
- 匹配结果布尔值、匹配类型、置信度范围、位置编号与上下文片段
|
||
|
||
```mermaid
|
||
classDiagram
|
||
class BrandMatcher {
|
||
+match(text) dict
|
||
-_extract_candidates(text) list
|
||
-_extract_position_and_context(text, keyword) tuple
|
||
}
|
||
class CompetitorDetector {
|
||
+detect(text, target_brand) list
|
||
}
|
||
BrandMatcher <.. CompetitorDetector : "组合使用"
|
||
```
|
||
|
||
图表来源
|
||
- [tests/test_citation_engine.py:6-54](file://tests/test_citation_engine.py#L6-L54)
|
||
- [backend/app/workers/citation_engine.py:19-146](file://backend/app/workers/citation_engine.py#L19-L146)
|
||
|
||
章节来源
|
||
- [tests/test_citation_engine.py:1-54](file://tests/test_citation_engine.py#L1-L54)
|
||
- [backend/app/workers/citation_engine.py:19-146](file://backend/app/workers/citation_engine.py#L19-L146)
|
||
|
||
## 依赖分析
|
||
- 测试与应用的耦合
|
||
- 通过依赖覆盖与patch解耦具体实现细节,提升测试稳定性
|
||
- 会话级mock调度器避免真实任务执行,保证测试幂等性
|
||
- 外部依赖
|
||
- PostgreSQL与Redis通过docker-compose提供,测试可选择独立数据库或内存数据库(如aiosqlite)以加速
|
||
- 日志与可观测性
|
||
- 引擎与调度器使用标准日志,便于定位问题;Alembic日志级别较低,避免噪声
|
||
|
||
```mermaid
|
||
graph LR
|
||
Py["pytest"] --> CF["conftest.py"]
|
||
CF --> APP["app/main.py"]
|
||
APP --> SCH["workers/scheduler.py"]
|
||
SCH --> CE["workers/citation_engine.py"]
|
||
CE --> DB["PostgreSQL/Redis"]
|
||
```
|
||
|
||
图表来源
|
||
- [tests/conftest.py:19-25](file://tests/conftest.py#L19-L25)
|
||
- [backend/app/main.py:10-21](file://backend/app/main.py#L10-L21)
|
||
- [backend/app/workers/scheduler.py:25-95](file://backend/app/workers/scheduler.py#L25-L95)
|
||
- [backend/app/workers/citation_engine.py:148-309](file://backend/app/workers/citation_engine.py#L148-L309)
|
||
|
||
章节来源
|
||
- [tests/conftest.py:19-25](file://tests/conftest.py#L19-L25)
|
||
- [backend/app/main.py:10-21](file://backend/app/main.py#L10-L21)
|
||
- [backend/app/workers/scheduler.py:25-95](file://backend/app/workers/scheduler.py#L25-L95)
|
||
- [backend/app/workers/citation_engine.py:148-309](file://backend/app/workers/citation_engine.py#L148-L309)
|
||
- [backend/alembic.ini:115-149](file://backend/alembic.ini#L115-L149)
|
||
|
||
## 性能考虑
|
||
- 测试速度
|
||
- 使用会话级fixture减少重复初始化成本
|
||
- 将外部依赖(数据库、缓存)置于容器中,避免每次重启
|
||
- 并发与异步
|
||
- 使用pytest-asyncio与AsyncClient,确保异步测试稳定
|
||
- 覆盖率
|
||
- 建议优先达到以下目标:行覆盖率≥80%、分支覆盖率≥70%、功能覆盖率≥90%
|
||
- 对关键路径(认证、查询、引用检测)进行重点覆盖
|
||
|
||
## 故障排查指南
|
||
- 常见问题
|
||
- 未覆盖依赖导致未认证访问失败:确认在测试中正确注入依赖覆盖
|
||
- patch目标不匹配:核对被patch的服务方法签名与调用路径
|
||
- 调度器干扰:确保会话级mock调度器生效
|
||
- 调试技巧
|
||
- 使用pytest调试选项:--pdb(进入调试器)、-v(详细输出)、-s(打印日志)
|
||
- 启用更详细的日志:在测试前设置日志级别,关注引擎与调度器日志
|
||
- 分离数据库:使用独立测试数据库或内存数据库(如aiosqlite)以避免数据污染
|
||
- 错误排查步骤
|
||
- 逐个缩小patch范围,确认业务方法是否被正确替换
|
||
- 检查JWT生成与解析流程,确保令牌有效
|
||
- 校验路由前缀与路径参数,避免404
|
||
|
||
章节来源
|
||
- [tests/conftest.py:19-25](file://tests/conftest.py#L19-L25)
|
||
- [tests/test_auth.py:88-104](file://tests/test_auth.py#L88-L104)
|
||
- [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43)
|
||
|
||
## 结论
|
||
通过统一的命名与结构规范、完善的夹具与mock策略、清晰的断言与覆盖率目标,以及合理的CI/CD与调试实践,GEO项目可以构建高质量、可维护的测试体系。建议在现有基础上逐步扩展覆盖率,并引入自动化CI流水线以保障质量。
|
||
|
||
## 附录
|
||
|
||
### 测试覆盖率要求与测量方法
|
||
- 行覆盖率(Line Coverage)
|
||
- 目标:≥80%
|
||
- 方法:使用覆盖率工具(如pytest-cov)生成报告,关注未覆盖的分支与异常路径
|
||
- 分支覆盖率(Branch Coverage)
|
||
- 目标:≥70%
|
||
- 方法:针对if/else、异常处理与权限判断路径分别设计用例
|
||
- 功能覆盖率(Functional Coverage)
|
||
- 目标:≥90%
|
||
- 方法:以API端点与核心算法(品牌匹配、竞争品牌检测)为功能域,确保每个功能域至少有一个正向与一个反向用例
|
||
|
||
### 持续集成配置建议
|
||
- 触发条件
|
||
- push到主分支与拉取请求
|
||
- 步骤建议
|
||
- 安装依赖(后端requirements.txt)
|
||
- 启动PostgreSQL与Redis(可复用docker-compose)
|
||
- 运行pytest并生成覆盖率报告
|
||
- 上传覆盖率与测试报告
|
||
- 参考文件
|
||
- 后端依赖清单:[backend/requirements.txt:31-35](file://backend/requirements.txt#L31-L35)
|
||
- 服务编排:[docker-compose.yml:1-71](file://docker-compose.yml#L1-L71)
|
||
|
||
章节来源
|
||
- [backend/requirements.txt:31-35](file://backend/requirements.txt#L31-L35)
|
||
- [docker-compose.yml:1-71](file://docker-compose.yml#L1-L71)
|
||
|
||
### 测试环境管理
|
||
- 数据库
|
||
- 开发:使用docker-compose提供的PostgreSQL
|
||
- 测试:可选独立测试库或内存数据库(如aiosqlite),避免跨测试污染
|
||
- 缓存
|
||
- Redis用于任务调度与缓存,测试中可直接使用容器版本
|
||
- 依赖注入
|
||
- 通过conftest中的依赖覆盖,确保测试中始终使用mock用户与禁用真实调度器
|
||
|
||
章节来源
|
||
- [docker-compose.yml:1-71](file://docker-compose.yml#L1-L71)
|
||
- [tests/conftest.py:19-50](file://tests/conftest.py#L19-L50)
|
||
|
||
### 测试数据管理
|
||
- 建议
|
||
- 使用fixture生成稳定的UUID与时间戳,确保可重复性
|
||
- 对于复杂对象(查询、引用记录),在fixture中集中定义字段默认值
|
||
- 对于CSV导出等场景,使用固定字符串片段进行断言,避免动态内容导致脆弱断言
|
||
|
||
章节来源
|
||
- [tests/test_queries.py:10-26](file://tests/test_queries.py#L10-L26)
|
||
- [tests/test_citations.py:8-20](file://tests/test_citations.py#L8-L20)
|
||
- [tests/test_citations.py:76-93](file://tests/test_citations.py#L76-L93)
|
||
|
||
### 测试报告生成
|
||
- 建议
|
||
- 使用pytest-cov生成HTML或XML报告,便于CI集成
|
||
- 在CI中保留报告产物,便于回溯历史趋势
|
||
- 日志配置
|
||
- Alembic日志级别已较低,可在测试中临时提高日志级别以辅助排查
|
||
|
||
章节来源
|
||
- [backend/alembic.ini:115-149](file://backend/alembic.ini#L115-L149) |