497 lines
23 KiB
Markdown
497 lines
23 KiB
Markdown
# 单元测试
|
||
|
||
<cite>
|
||
**本文档引用的文件**
|
||
- [tests/conftest.py](file://tests/conftest.py)
|
||
- [tests/test_auth.py](file://tests/test_auth.py)
|
||
- [tests/test_citation_engine.py](file://tests/test_citation_engine.py)
|
||
- [tests/test_citations.py](file://tests/test_citations.py)
|
||
- [tests/test_queries.py](file://tests/test_queries.py)
|
||
- [backend/app/main.py](file://backend/app/main.py)
|
||
- [backend/app/api/deps.py](file://backend/app/api/deps.py)
|
||
- [backend/app/api/auth.py](file://backend/app/api/auth.py)
|
||
- [backend/app/api/citations.py](file://backend/app/api/citations.py)
|
||
- [backend/app/api/queries.py](file://backend/app/api/queries.py)
|
||
- [backend/app/workers/citation_engine.py](file://backend/app/workers/citation_engine.py)
|
||
- [backend/app/services/auth.py](file://backend/app/services/auth.py)
|
||
- [backend/app/models/user.py](file://backend/app/models/user.py)
|
||
- [backend/app/models/citation_record.py](file://backend/app/models/citation_record.py)
|
||
</cite>
|
||
|
||
## 目录
|
||
1. [简介](#简介)
|
||
2. [项目结构](#项目结构)
|
||
3. [核心组件](#核心组件)
|
||
4. [架构总览](#架构总览)
|
||
5. [详细组件分析](#详细组件分析)
|
||
6. [依赖分析](#依赖分析)
|
||
7. [性能考虑](#性能考虑)
|
||
8. [故障排查指南](#故障排查指南)
|
||
9. [结论](#结论)
|
||
10. [附录](#附录)
|
||
|
||
## 简介
|
||
本文件系统性梳理 GEO 项目的单元测试实现,覆盖认证模块、引用引擎、查询处理与引用数据四大模块的测试用例设计与最佳实践。文档重点说明测试夹具(fixture)的使用方法(如 mock_user、auth_token、auth_headers、override_get_current_user),断言编写方式与覆盖率建议,并提供异步函数、依赖注入与错误处理的测试示例路径与流程图。
|
||
|
||
## 项目结构
|
||
测试目录位于仓库根目录 tests/,采用按功能分模块组织的方式,分别对应后端 API 层与核心业务组件:
|
||
- 认证模块测试:tests/test_auth.py
|
||
- 引用引擎测试:tests/test_citation_engine.py
|
||
- 查询处理测试:tests/test_queries.py
|
||
- 引用数据测试:tests/test_citations.py
|
||
- 测试夹具与异步客户端:tests/conftest.py
|
||
- 后端应用入口与路由:backend/app/main.py、backend/app/api/*.py
|
||
- 核心业务组件:backend/app/workers/citation_engine.py
|
||
- 服务层与模型:backend/app/services/*、backend/app/models/*
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "测试层"
|
||
T1["tests/test_auth.py"]
|
||
T2["tests/test_citation_engine.py"]
|
||
T3["tests/test_queries.py"]
|
||
T4["tests/test_citations.py"]
|
||
C["tests/conftest.py"]
|
||
end
|
||
subgraph "后端应用"
|
||
M["backend/app/main.py"]
|
||
A1["backend/app/api/auth.py"]
|
||
A2["backend/app/api/queries.py"]
|
||
A3["backend/app/api/citations.py"]
|
||
D["backend/app/api/deps.py"]
|
||
S1["backend/app/services/auth.py"]
|
||
W["backend/app/workers/citation_engine.py"]
|
||
U["backend/app/models/user.py"]
|
||
CR["backend/app/models/citation_record.py"]
|
||
end
|
||
T1 --> A1
|
||
T2 --> W
|
||
T3 --> A2
|
||
T4 --> A3
|
||
T1 --> D
|
||
T2 --> W
|
||
T3 --> D
|
||
T4 --> D
|
||
A1 --> S1
|
||
A2 --> D
|
||
A3 --> D
|
||
D --> U
|
||
W --> CR
|
||
M --> A1
|
||
M --> A2
|
||
M --> A3
|
||
C --> M
|
||
```
|
||
|
||
图表来源
|
||
- [tests/conftest.py:1-71](file://tests/conftest.py#L1-L71)
|
||
- [backend/app/main.py:1-48](file://backend/app/main.py#L1-L48)
|
||
- [backend/app/api/auth.py:1-43](file://backend/app/api/auth.py#L1-L43)
|
||
- [backend/app/api/queries.py:1-86](file://backend/app/api/queries.py#L1-L86)
|
||
- [backend/app/api/citations.py:1-78](file://backend/app/api/citations.py#L1-L78)
|
||
- [backend/app/api/deps.py:1-43](file://backend/app/api/deps.py#L1-L43)
|
||
- [backend/app/services/auth.py:1-69](file://backend/app/services/auth.py#L1-L69)
|
||
- [backend/app/workers/citation_engine.py:1-309](file://backend/app/workers/citation_engine.py#L1-L309)
|
||
- [backend/app/models/user.py:1-41](file://backend/app/models/user.py#L1-L41)
|
||
- [backend/app/models/citation_record.py:1-42](file://backend/app/models/citation_record.py#L1-L42)
|
||
|
||
章节来源
|
||
- [tests/conftest.py:1-71](file://tests/conftest.py#L1-L71)
|
||
- [backend/app/main.py:1-48](file://backend/app/main.py#L1-L48)
|
||
|
||
## 核心组件
|
||
本节概述各模块测试关注点与测试夹具的作用范围。
|
||
|
||
- 认证模块测试(tests/test_auth.py)
|
||
- 注册成功/重复邮箱、登录成功/密码错误、用户信息获取(含未认证场景)
|
||
- 使用夹具:mock_registered_user、async_client、patch 注入服务层行为
|
||
- 关键断言:HTTP 状态码、响应体字段、错误详情消息
|
||
|
||
- 引用引擎测试(tests/test_citation_engine.py)
|
||
- 品牌匹配器精确/别名/模糊/无匹配;竞争品牌检测;引用位置与上下文提取
|
||
- 使用夹具:直接构造类实例进行纯函数式断言,无需外部依赖
|
||
|
||
- 查询处理测试(tests/test_queries.py)
|
||
- 创建查询成功/超出限额、列出查询、更新查询、删除查询、查询不存在/不属于他人
|
||
- 使用夹具:mock_query、async_client、patch 服务层返回值或异常
|
||
|
||
- 引用数据测试(tests/test_citations.py)
|
||
- 获取引用列表、统计信息、CSV 导出(MIME 类型、附件头、内容片段)
|
||
|
||
- 测试夹具(tests/conftest.py)
|
||
- mock_scheduler:屏蔽后台调度器,避免真实任务影响测试
|
||
- mock_user:模拟认证用户对象,包含 id、邮箱、计划、配额等
|
||
- override_get_current_user:重写依赖 get_current_user,使路由自动获得认证用户
|
||
- auth_token:基于 mock_user 生成 JWT
|
||
- auth_headers:组装 Authorization Bearer 头
|
||
- async_client:ASGI 异步 HTTP 客户端,用于端到端测试
|
||
|
||
章节来源
|
||
- [tests/test_auth.py:1-104](file://tests/test_auth.py#L1-L104)
|
||
- [tests/test_citation_engine.py:1-54](file://tests/test_citation_engine.py#L1-L54)
|
||
- [tests/test_queries.py:1-154](file://tests/test_queries.py#L1-L154)
|
||
- [tests/test_citations.py:1-93](file://tests/test_citations.py#L1-L93)
|
||
- [tests/conftest.py:19-71](file://tests/conftest.py#L19-L71)
|
||
|
||
## 架构总览
|
||
下图展示测试夹具与被测组件之间的交互关系,以及异步 HTTP 客户端如何驱动 FastAPI 路由与依赖注入。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Test as "测试用例"
|
||
participant Fixture as "测试夹具(conftest.py)"
|
||
participant Client as "AsyncClient"
|
||
participant App as "FastAPI 应用(main.py)"
|
||
participant Dep as "依赖注入(deps.py)"
|
||
participant Service as "服务层(API 路由)"
|
||
participant Model as "模型(models)"
|
||
Test->>Fixture : 请求夹具(mock_user/override_get_current_user/...)
|
||
Fixture-->>Test : 返回夹具对象
|
||
Test->>Client : 发起 HTTP 请求
|
||
Client->>App : ASGI 请求
|
||
App->>Dep : 解析 OAuth2 Bearer Token
|
||
Dep-->>App : 返回当前用户(get_current_user)
|
||
App->>Service : 调用路由处理器
|
||
Service->>Model : 数据库读写/查询
|
||
Service-->>App : 返回响应数据
|
||
App-->>Client : HTTP 响应
|
||
Client-->>Test : 断言结果
|
||
```
|
||
|
||
图表来源
|
||
- [tests/conftest.py:28-71](file://tests/conftest.py#L28-L71)
|
||
- [backend/app/main.py:38-42](file://backend/app/main.py#L38-L42)
|
||
- [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43)
|
||
- [backend/app/api/auth.py:13-43](file://backend/app/api/auth.py#L13-L43)
|
||
- [backend/app/api/queries.py:15-86](file://backend/app/api/queries.py#L15-L86)
|
||
- [backend/app/api/citations.py:25-78](file://backend/app/api/citations.py#L25-L78)
|
||
|
||
## 详细组件分析
|
||
|
||
### 认证模块测试
|
||
- 测试要点
|
||
- 注册接口:成功返回 201,响应包含邮箱与姓名;重复邮箱抛出 400
|
||
- 登录接口:成功返回 200,包含 access_token 与用户信息;错误凭据返回 401
|
||
- 用户信息接口:已认证返回 200,未认证返回 401
|
||
- 测试夹具
|
||
- mock_registered_user:模拟注册成功的用户对象
|
||
- override_get_current_user:通过依赖覆盖提供认证用户
|
||
- auth_headers:携带 Bearer Token 的请求头
|
||
- async_client:ASGI 异步客户端
|
||
- 断言策略
|
||
- 状态码断言:201/200/400/401
|
||
- 响应体字段断言:access_token、token_type、user.email 等
|
||
- 错误详情断言:detail 中包含特定提示
|
||
- 异步与依赖注入示例路径
|
||
- [tests/test_auth.py:25-40](file://tests/test_auth.py#L25-L40)
|
||
- [tests/test_auth.py:62-73](file://tests/test_auth.py#L62-L73)
|
||
- [tests/test_auth.py:88-104](file://tests/test_auth.py#L88-L104)
|
||
- [tests/conftest.py:28-62](file://tests/conftest.py#L28-L62)
|
||
- [backend/app/api/auth.py:13-43](file://backend/app/api/auth.py#L13-L43)
|
||
- [backend/app/services/auth.py:37-69](file://backend/app/services/auth.py#L37-L69)
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant T as "测试用例(test_auth.py)"
|
||
participant F as "夹具(conftest.py)"
|
||
participant AC as "AsyncClient"
|
||
participant R as "路由(auth.py)"
|
||
participant S as "服务(auth.py)"
|
||
participant DB as "数据库"
|
||
T->>F : 获取 mock_registered_user/override_get_current_user/auth_headers
|
||
T->>AC : POST /api/v1/auth/register
|
||
AC->>R : 路由处理器
|
||
R->>S : register_user(...)
|
||
S->>DB : 查询/插入用户
|
||
S-->>R : 返回用户对象
|
||
R-->>AC : 201 + 用户信息
|
||
AC-->>T : 断言状态码与字段
|
||
```
|
||
|
||
图表来源
|
||
- [tests/test_auth.py:25-40](file://tests/test_auth.py#L25-L40)
|
||
- [tests/conftest.py:28-62](file://tests/conftest.py#L28-L62)
|
||
- [backend/app/api/auth.py:13-19](file://backend/app/api/auth.py#L13-L19)
|
||
- [backend/app/services/auth.py:37-52](file://backend/app/services/auth.py#L37-L52)
|
||
|
||
章节来源
|
||
- [tests/test_auth.py:1-104](file://tests/test_auth.py#L1-L104)
|
||
- [tests/conftest.py:28-62](file://tests/conftest.py#L28-L62)
|
||
- [backend/app/api/auth.py:13-43](file://backend/app/api/auth.py#L13-L43)
|
||
- [backend/app/services/auth.py:37-69](file://backend/app/services/auth.py#L37-L69)
|
||
|
||
### 引用引擎测试
|
||
- 测试要点
|
||
- BrandMatcher:精确匹配置信度 1.0、别名匹配置信度 0.9、模糊匹配阈值 >0.4、无匹配返回 False/None/0.0
|
||
- CompetitorDetector:在文本中识别除目标品牌外的竞争品牌
|
||
- 引用位置与上下文:按段落定位首次出现位置并截取上下文片段
|
||
- 测试夹具
|
||
- 直接构造 BrandMatcher/CompetitorDetector 实例,无需外部依赖
|
||
- 断言策略
|
||
- 字典键断言:cited、match_type、confidence、position、citation_text
|
||
- 列表断言:competitor_brands 包含预期品牌且不含目标品牌
|
||
- 示例路径
|
||
- [tests/test_citation_engine.py:6-37](file://tests/test_citation_engine.py#L6-L37)
|
||
- [tests/test_citation_engine.py:39-45](file://tests/test_citation_engine.py#L39-L45)
|
||
- [tests/test_citation_engine.py:47-54](file://tests/test_citation_engine.py#L47-L54)
|
||
- [backend/app/workers/citation_engine.py:19-120](file://backend/app/workers/citation_engine.py#L19-L120)
|
||
- [backend/app/workers/citation_engine.py:122-146](file://backend/app/workers/citation_engine.py#L122-L146)
|
||
|
||
```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 : "在引擎中协作"
|
||
```
|
||
|
||
图表来源
|
||
- [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)
|
||
|
||
### 查询处理测试
|
||
- 测试要点
|
||
- 创建查询:成功返回 201,响应包含关键词与目标品牌;超出配额抛出 403
|
||
- 列出查询:返回 items 与 total
|
||
- 更新查询:返回更新后的关键词与频率
|
||
- 删除查询:返回 204
|
||
- 查询不存在/不属于他人:返回 404
|
||
- 测试夹具
|
||
- mock_query:模拟查询对象,包含 id、keyword、target_brand、platforms、frequency、status 等
|
||
- override_get_current_user、auth_headers、async_client
|
||
- 断言策略
|
||
- 状态码断言:201/200/204/403/404
|
||
- 响应体字段断言:keyword、frequency、detail 等
|
||
- 示例路径
|
||
- [tests/test_queries.py:30-48](file://tests/test_queries.py#L30-L48)
|
||
- [tests/test_queries.py:51-71](file://tests/test_queries.py#L51-L71)
|
||
- [tests/test_queries.py:74-84](file://tests/test_queries.py#L74-L84)
|
||
- [tests/test_queries.py:87-113](file://tests/test_queries.py#L87-L113)
|
||
- [tests/test_queries.py:116-125](file://tests/test_queries.py#L116-L125)
|
||
- [tests/test_queries.py:128-139](file://tests/test_queries.py#L128-L139)
|
||
- [tests/test_queries.py:143-154](file://tests/test_queries.py#L143-L154)
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant T as "测试用例(test_queries.py)"
|
||
participant F as "夹具(conftest.py)"
|
||
participant AC as "AsyncClient"
|
||
participant R as "路由(queries.py)"
|
||
participant S as "服务层"
|
||
participant DB as "数据库"
|
||
T->>F : 获取 mock_query/override_get_current_user/auth_headers
|
||
T->>AC : POST /api/v1/queries/
|
||
AC->>R : 路由处理器
|
||
R->>S : create_query(...)
|
||
S->>DB : 插入查询记录
|
||
S-->>R : 返回查询对象
|
||
R-->>AC : 201 + 查询信息
|
||
AC-->>T : 断言状态码与字段
|
||
```
|
||
|
||
图表来源
|
||
- [tests/test_queries.py:30-48](file://tests/test_queries.py#L30-L48)
|
||
- [backend/app/api/queries.py:26-39](file://backend/app/api/queries.py#L26-L39)
|
||
|
||
章节来源
|
||
- [tests/test_queries.py:1-154](file://tests/test_queries.py#L1-L154)
|
||
- [backend/app/api/queries.py:15-86](file://backend/app/api/queries.py#L15-L86)
|
||
|
||
### 引用数据测试
|
||
- 测试要点
|
||
- 获取引用列表:返回 items 与 total,包含 platform 与 cited 等字段
|
||
- 统计信息:返回总量、引用率、按平台分布与趋势
|
||
- CSV 导出:返回 text/csv 内容类型与附件头,内容包含平台信息
|
||
- 测试夹具
|
||
- mock_citation_record:模拟引用记录对象
|
||
- override_get_current_user、auth_headers、async_client
|
||
- 断言策略
|
||
- 状态码断言:200
|
||
- 响应体断言:total、items、by_platform、trend 等
|
||
- 响应头断言:Content-Type 以 text/csv 开头、Content-Disposition 含 attachment
|
||
- 示例路径
|
||
- [tests/test_citations.py:24-41](file://tests/test_citations.py#L24-L41)
|
||
- [tests/test_citations.py:44-73](file://tests/test_citations.py#L44-L73)
|
||
- [tests/test_citations.py:76-93](file://tests/test_citations.py#L76-L93)
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant T as "测试用例(test_citations.py)"
|
||
participant F as "夹具(conftest.py)"
|
||
participant AC as "AsyncClient"
|
||
participant R as "路由(citations.py)"
|
||
participant S as "服务层"
|
||
participant DB as "数据库"
|
||
T->>F : 获取 mock_citation_record/override_get_current_user/auth_headers
|
||
T->>AC : GET /api/v1/citations/stats
|
||
AC->>R : 路由处理器
|
||
R->>S : get_citation_stats(...)
|
||
S->>DB : 聚合统计
|
||
S-->>R : 返回统计结果
|
||
R-->>AC : 200 + 统计信息
|
||
AC-->>T : 断言字段与头
|
||
```
|
||
|
||
图表来源
|
||
- [tests/test_citations.py:44-73](file://tests/test_citations.py#L44-L73)
|
||
- [backend/app/api/citations.py:49-56](file://backend/app/api/citations.py#L49-L56)
|
||
|
||
章节来源
|
||
- [tests/test_citations.py:1-93](file://tests/test_citations.py#L1-L93)
|
||
- [backend/app/api/citations.py:25-78](file://backend/app/api/citations.py#L25-L78)
|
||
|
||
### 测试夹具详解与最佳实践
|
||
- mock_scheduler
|
||
- 作用:屏蔽后台调度器,避免真实任务启动影响测试稳定性
|
||
- 使用:在会话级 autouse fixture 中替换 app.main.query_scheduler 的 start/shutdown
|
||
- 参考路径:[tests/conftest.py:19-26](file://tests/conftest.py#L19-L26)
|
||
- mock_user
|
||
- 作用:提供认证用户对象,包含 id、email、name、plan、max_queries、is_active 等
|
||
- 使用:作为 override_get_current_user 的数据源
|
||
- 参考路径:[tests/conftest.py:29-39](file://tests/conftest.py#L29-L39)
|
||
- override_get_current_user
|
||
- 作用:重写依赖 get_current_user,使路由自动解析到 mock_user
|
||
- 使用:在测试函数参数中注入,结束后清理依赖覆盖
|
||
- 参考路径:[tests/conftest.py:42-50](file://tests/conftest.py#L42-L50)
|
||
- auth_token
|
||
- 作用:基于 mock_user 的 id 生成 JWT
|
||
- 使用:配合 auth_headers 进行认证请求
|
||
- 参考路径:[tests/conftest.py:54-56](file://tests/conftest.py#L54-L56)
|
||
- auth_headers
|
||
- 作用:组装 Authorization: Bearer <token> 请求头
|
||
- 使用:GET/POST/PUT/DELETE 等请求统一携带
|
||
- 参考路径:[tests/conftest.py:60-62](file://tests/conftest.py#L60-L62)
|
||
- async_client
|
||
- 作用:ASGI 异步 HTTP 客户端,用于端到端测试
|
||
- 使用:with 上下文管理器确保生命周期内正确创建与释放
|
||
- 参考路径:[tests/conftest.py:65-71](file://tests/conftest.py#L65-L71)
|
||
|
||
最佳实践
|
||
- 尽量使用 patch 替换服务层或外部依赖,避免真实网络或数据库调用
|
||
- 对于需要认证的路由,优先使用 override_get_current_user 与 auth_headers
|
||
- 对于后台任务,使用 mock_scheduler 屏蔽真实调度器
|
||
- 对于异步函数,使用 pytest.mark.asyncio 并通过 AsyncClient 发起请求
|
||
- 清理:在依赖覆盖后及时 pop,避免影响其他测试
|
||
|
||
章节来源
|
||
- [tests/conftest.py:19-71](file://tests/conftest.py#L19-L71)
|
||
|
||
### 异步函数、依赖注入与错误处理测试示例
|
||
- 异步函数
|
||
- 使用 pytest.mark.asyncio 标记测试函数
|
||
- 使用 ASGI AsyncClient 发起请求
|
||
- 参考路径:[tests/test_auth.py:25-40](file://tests/test_auth.py#L25-L40)、[tests/test_queries.py:30-48](file://tests/test_queries.py#L30-L48)
|
||
- 依赖注入
|
||
- 通过 app.dependency_overrides 临时替换 get_current_user
|
||
- 参考路径:[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)
|
||
- 错误处理
|
||
- 通过 patch 抛出异常或返回 None,验证路由的 HTTP 异常与错误详情
|
||
- 参考路径:[tests/test_auth.py:43-58](file://tests/test_auth.py#L43-L58)、[tests/test_queries.py:51-71](file://tests/test_queries.py#L51-L71)
|
||
|
||
章节来源
|
||
- [tests/test_auth.py:25-58](file://tests/test_auth.py#L25-L58)
|
||
- [tests/test_queries.py:51-71](file://tests/test_queries.py#L51-L71)
|
||
- [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)
|
||
|
||
### 测试数据准备与清理
|
||
- 准备
|
||
- 使用 AsyncMock 构造模型对象(用户、查询、引用记录),设置必要字段
|
||
- 使用 patch 模拟服务层返回值或异常
|
||
- 使用 override_get_current_user 提供认证上下文
|
||
- 清理
|
||
- 测试结束后 pop 依赖覆盖,避免污染其他测试
|
||
- 使用 with 上下文管理 async_client 生命周期
|
||
- 参考路径
|
||
- [tests/conftest.py:42-50](file://tests/conftest.py#L42-L50)、[tests/conftest.py:65-71](file://tests/conftest.py#L65-L71)
|
||
- [tests/test_citations.py:8-21](file://tests/test_citations.py#L8-L21)、[tests/test_queries.py:10-27](file://tests/test_queries.py#L10-L27)
|
||
|
||
章节来源
|
||
- [tests/conftest.py:42-50](file://tests/conftest.py#L42-L50)
|
||
- [tests/conftest.py:65-71](file://tests/conftest.py#L65-L71)
|
||
- [tests/test_citations.py:8-21](file://tests/test_citations.py#L8-L21)
|
||
- [tests/test_queries.py:10-27](file://tests/test_queries.py#L10-L27)
|
||
|
||
## 依赖分析
|
||
- 测试对应用层的依赖
|
||
- 测试通过 ASGI AsyncClient 直接调用 FastAPI 路由,路由再依赖 get_current_user 解析 JWT
|
||
- get_current_user 依赖 OAuth2PasswordBearer 与 verify_token,最终查询数据库 User 表
|
||
- 测试对服务层的依赖
|
||
- 通过 patch 替换服务层函数(如 register_user、authenticate_user、create_query 等)
|
||
- 测试对业务组件的依赖
|
||
- 引擎测试直接构造 BrandMatcher/CompetitorDetector,不依赖外部服务
|
||
|
||
```mermaid
|
||
graph LR
|
||
T["测试用例"] --> C["conftest.py 夹具"]
|
||
C --> A["FastAPI 路由"]
|
||
A --> D["依赖注入(get_current_user)"]
|
||
D --> S["服务层"]
|
||
S --> DB["数据库"]
|
||
T --> E["引用引擎(直接类实例)"]
|
||
```
|
||
|
||
图表来源
|
||
- [tests/conftest.py:28-71](file://tests/conftest.py#L28-L71)
|
||
- [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43)
|
||
- [backend/app/api/auth.py:13-43](file://backend/app/api/auth.py#L13-L43)
|
||
- [backend/app/api/queries.py:26-39](file://backend/app/api/queries.py#L26-L39)
|
||
- [backend/app/workers/citation_engine.py:19-146](file://backend/app/workers/citation_engine.py#L19-L146)
|
||
|
||
章节来源
|
||
- [tests/conftest.py:28-71](file://tests/conftest.py#L28-L71)
|
||
- [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43)
|
||
- [backend/app/api/auth.py:13-43](file://backend/app/api/auth.py#L13-L43)
|
||
- [backend/app/api/queries.py:26-39](file://backend/app/api/queries.py#L26-L39)
|
||
- [backend/app/workers/citation_engine.py:19-146](file://backend/app/workers/citation_engine.py#L19-L146)
|
||
|
||
## 性能考虑
|
||
- 使用 ASGI AsyncClient 避免真实网络往返,提升测试速度
|
||
- 通过 patch 替换外部服务调用,减少 IO 与等待时间
|
||
- 使用 mock_scheduler 屏蔽后台任务,避免并发与定时任务干扰
|
||
- 对纯函数(引擎类)直接实例化断言,避免数据库与网络依赖
|
||
|
||
## 故障排查指南
|
||
- 401 未认证
|
||
- 检查是否正确注入 override_get_current_user 与 auth_headers
|
||
- 参考路径:[tests/test_auth.py:88-104](file://tests/test_auth.py#L88-L104)、[tests/conftest.py:42-62](file://tests/conftest.py#L42-L62)
|
||
- 403 超出配额
|
||
- 检查服务层是否抛出 PermissionError,测试中通过 patch 触发
|
||
- 参考路径:[tests/test_queries.py:51-71](file://tests/test_queries.py#L51-L71)
|
||
- 404 不存在/不属于他人
|
||
- 检查 get_query 返回 None 的分支,确认依赖覆盖与参数
|
||
- 参考路径:[tests/test_queries.py:128-139](file://tests/test_queries.py#L128-L139)、[tests/test_queries.py:143-154](file://tests/test_queries.py#L143-L154)
|
||
- CSV 导出问题
|
||
- 检查 Content-Type 与 Content-Disposition 头,以及响应体是否包含平台信息
|
||
- 参考路径:[tests/test_citations.py:76-93](file://tests/test_citations.py#L76-L93)
|
||
|
||
章节来源
|
||
- [tests/test_auth.py:88-104](file://tests/test_auth.py#L88-L104)
|
||
- [tests/test_queries.py:51-71](file://tests/test_queries.py#L51-L71)
|
||
- [tests/test_queries.py:128-154](file://tests/test_queries.py#L128-L154)
|
||
- [tests/test_citations.py:76-93](file://tests/test_citations.py#L76-L93)
|
||
- [tests/conftest.py:42-62](file://tests/conftest.py#L42-L62)
|
||
|
||
## 结论
|
||
本测试体系通过夹具与 patch 有效隔离外部依赖,结合 ASGI 异步客户端完成端到端验证。认证、查询、引用数据三大模块均覆盖成功与错误路径,引擎模块以纯函数形式保证可测试性与高内聚。建议持续补充边界条件与并发场景测试,逐步提升整体覆盖率。
|
||
|
||
## 附录
|
||
- 测试覆盖率建议
|
||
- 路由层:100%(含错误分支)
|
||
- 服务层:100%(含异常路径)
|
||
- 引擎类:100%(精确/别名/模糊/无匹配、位置与上下文)
|
||
- 数据模型:100%(字段与索引覆盖)
|
||
- 参考文件
|
||
- [backend/app/models/user.py:1-41](file://backend/app/models/user.py#L1-L41)
|
||
- [backend/app/models/citation_record.py:1-42](file://backend/app/models/citation_record.py#L1-L42)
|
||
- [backend/app/workers/citation_engine.py:1-309](file://backend/app/workers/citation_engine.py#L1-L309)
|
||
- [backend/app/api/deps.py:1-43](file://backend/app/api/deps.py#L1-L43)
|
||
- [backend/app/services/auth.py:1-69](file://backend/app/services/auth.py#L1-L69) |