You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
12 KiB
12 KiB
测试最佳实践整合到 testing.md 规范
日期: 2026-02-02
类型: Documentation
影响范围: 测试规范文档、开发流程
概述
将 pytest 事件循环修复的最佳实践整合到项目测试规范文档(jointo-tech-stack skill 的 testing.md),为团队提供完整的测试指南。
背景
在完成 pytest 事件循环修复 后,需要将修复方案和最佳实践整合到项目的测试规范中,确保:
- 团队成员了解正确的测试配置方式
- 新加入的开发者可以快速上手
- 避免重复遇到相同的问题
- 建立统一的测试标准
整合方案
采用混合方案(方案 3):
- 创建新章节提供完整的修复方案和原理说明
- 更新现有章节确保代码示例是最新的
- 提供交叉引用,易于查找
更新内容
1. 新增章节:FastAPI + asyncpg + httpx.AsyncClient 事件循环修复
位置:在 "常用 Fixtures 使用指南" 之前
内容:
- 问题描述和错误表现
- 根本原因分析
- 完整修复方案(5 个步骤)
- 修复效果对比
- 事件循环作用域对比表
- 相关文档链接
核心修复步骤:
- Session 级别的事件循环
- Session 级别的数据库引擎
- Function 级别的数据库会话
- AsyncClient Fixture
- JWT Token 唯一性处理
2. 更新 "常用 Fixtures 使用指南"
更新内容:
- 在
test_authfixture 说明中添加 JWT Token 唯一性处理 - 添加
asyncio.sleep(1.1)的使用说明 - 解释为什么需要延迟(JWT 时间戳精度为秒)
示例代码:
@pytest_asyncio.fixture
async def test_auth(async_client: AsyncClient) -> dict:
"""通过登录获取测试用户的认证信息
⚠️ JWT Token 唯一性:
添加 asyncio.sleep(1.1) 确保每次登录生成不同的 token
(JWT 包含时间戳,精度为秒)
"""
# 添加延迟确保 token 唯一
await asyncio.sleep(1.1)
response = await async_client.post(...)
...
3. 更新 "共享 Fixtures"
更新内容:
- 使用 session 级别的
event_loopfixture - 使用 session 级别的
async_enginefixture - 使用 function 级别的
db_sessionfixture - 移除
async_client的lifespan参数(httpx 0.26+ 已移除) - 添加详细的注释说明
关键改进:
# 1️⃣ Session 级别的事件循环
@pytest.fixture(scope="session")
def event_loop():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) # ✅ 关键
yield loop
loop.close()
# 2️⃣ Session 级别的数据库引擎
@pytest_asyncio.fixture(scope="session")
async def async_engine():
engine = create_async_engine(...)
yield engine
await engine.dispose()
# 3️⃣ Function 级别的数据库会话
@pytest_asyncio.fixture
async def db_session(async_engine):
async_session = sessionmaker(
async_engine,
class_=AsyncSession,
expire_on_commit=False, # ✅ 关键
...
)
async with async_session() as session:
try:
yield session
finally:
await session.rollback()
await session.close()
4. 更新 "常见问题与解决方案 > 1. 事件循环问题"
更新内容:
- 添加指向新章节的链接
- 提供快速解决方案
- 解释为什么需要 session 级别的 event_loop 和 async_engine
- 解释为什么 db_session 是 function 级别
改进前:
- 建议使用 function 级别的 engine(错误)
- 建议使用 dependency override(不是最佳方案)
改进后:
- 建议使用 session 级别的 event_loop 和 async_engine(正确)
- 提供完整的修复方案链接
- 解释原理和最佳实践
技术细节
事件循环作用域对比
| 作用域 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| function | 测试完全隔离 | asyncpg pool 冲突 | 单元测试(不使用 asyncpg) |
| session | 避免 pool 冲突 | 需要手动清理 | 集成测试(FastAPI + asyncpg) |
JWT Token 唯一性
问题:JWT token 包含时间戳(精度为秒),如果在同一秒内多次登录,会生成相同的 token。
解决方案:
await asyncio.sleep(1.1) # 确保跨越秒边界
为什么是 1.1 秒:
- JWT 时间戳精度为秒
- 1.1 秒确保跨越秒边界
- 避免边界情况(如 0.999 秒可能不够)
文档结构
更新后的章节顺序
- 测试金字塔
- 测试目录结构
- FastAPI + asyncpg + httpx.AsyncClient 事件循环修复(新增)
- 常用 Fixtures 使用指南(更新)
- 集成测试认证模式
- API 响应格式处理
- 常见字段名称规范
- 测试类型
- 测试覆盖率要求
- 测试策略
- 共享 Fixtures(更新)
- 测试隔离原则
- 测试环境
- 最佳实践
- 故障排查
- 持续集成
- 相关文档
- 测试代码最佳实践
- 常见问题与解决方案(更新)
- 测试覆盖率要求
- 测试最佳实践总结
- 特定服务测试文档
- 参考资源
交叉引用
- 新章节 → 相关 Changelog 文档
- 常见问题 → 新章节(完整修复方案)
- 共享 Fixtures → 新章节(原理说明)
影响范围
受益对象
-
新加入的开发者:
- 快速了解正确的测试配置
- 避免常见的事件循环问题
- 学习最佳实践
-
现有团队成员:
- 统一测试标准
- 参考完整的修复方案
- 提高测试质量
-
未来维护者:
- 完整的文档记录
- 清晰的原理说明
- 易于理解和维护
文档更新
- ✅
.claude/skills/jointo-tech-stack/references/testing.md- 主要更新 - ✅
docs/server/changelogs/2026-02-02-pytest-event-loop-fix.md- 参考文档 - ✅
docs/server/changelogs/2026-02-02-testing-best-practices-integration.md- 本文档
最佳实践总结
1. 事件循环配置
✅ 推荐:
@pytest.fixture(scope="session")
def event_loop():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
yield loop
loop.close()
❌ 避免:
@pytest.fixture(scope="function") # 会导致 asyncpg pool 冲突
def event_loop():
...
2. 数据库引擎配置
✅ 推荐:
@pytest_asyncio.fixture(scope="session")
async def async_engine():
engine = create_async_engine(...)
yield engine
await engine.dispose()
❌ 避免:
@pytest_asyncio.fixture(scope="function") # 性能差,可能冲突
async def async_engine():
...
3. 数据库会话配置
✅ 推荐:
@pytest_asyncio.fixture # function 级别
async def db_session(async_engine):
async_session = sessionmaker(
async_engine,
expire_on_commit=False, # ✅ 关键
...
)
async with async_session() as session:
try:
yield session
finally:
await session.rollback() # ✅ 自动回滚
await session.close()
4. JWT Token 唯一性
✅ 推荐:
async def login_and_get_token(...):
await asyncio.sleep(1.1) # ✅ 确保 token 唯一
response = await async_client.post(...)
...
验证结果
测试通过率
- ✅ 集成测试:8/8 全部通过
- ✅ 单元测试:11/11 全部通过
- ✅ 总计:19/19 全部通过
文档完整性
- ✅ 新章节创建完成
- ✅ 现有章节更新完成
- ✅ 交叉引用添加完成
- ✅ 代码示例更新完成
相关文档
文档优化(方案 3:混合方案)
优化背景
testing.md 文件大小达到 80K(3009 行),成为所有 reference 文件中最大的,可能影响 AI 加载和理解效率。
优化方案
采用混合方案:
- 创建专题文件
testing-event-loop-fix.md存放详细内容 - 精简
testing.md主文件,保留核心配置和快速参考 - 利用交叉引用连接主文件和专题文件
优化结果
文件大小对比
| 文件 | 优化前 | 优化后 | 变化 |
|---|---|---|---|
| testing.md | 80K (3009 行) | 76K (2858 行) | -4K (-151 行) |
| testing-event-loop-fix.md | - | 14K (546 行) | +14K (+546 行) |
| 总计 | 80K | 90K | +10K |
说明:
- testing.md 减少了 5%(-4K)
- 新增专题文件 14K
- 总大小增加 10K,但结构更清晰
结构优化
testing.md(主文件):
- ✅ 保留核心配置模板
- ✅ 保留快速参考指南
- ✅ 保留 Fixtures 使用指南
- ✅ 添加指向专题文件的链接
- ❌ 移除详细的原理说明
- ❌ 移除重复的代码示例
testing-event-loop-fix.md(专题文件):
- ✅ 完整的问题分析
- ✅ 详细的修复步骤
- ✅ 技术原理深入讲解
- ✅ 常见误区和验证方法
- ✅ 独立的目录结构
优化效果
1. 主文件更简洁
## FastAPI + asyncpg + httpx.AsyncClient 事件循环修复
### 快速参考
**问题**:`RuntimeError: Task got Future attached to a different loop`
**核心解决方案**:使用 session 级别的事件循环和数据库引擎
[核心代码示例...]
**完整文档**:请参考 [FastAPI + asyncpg + httpx.AsyncClient 事件循环修复详解](./testing-event-loop-fix.md)
2. 专题文件更详细
# FastAPI + asyncpg + httpx.AsyncClient 事件循环修复详解
## 目录
- 问题描述
- 根本原因分析
- 完整修复方案
- 修复效果对比
- 技术原理深入
- 常见误区
- 验证方法
3. 导航更清晰
- 主文件 → 专题文件(深入学习)
- 主文件 → Changelog(历史记录)
- 专题文件 → 主文件(返回概览)
用户体验改进
快速查找(主文件)
- ✅ 核心配置一目了然
- ✅ 快速参考代码示例
- ✅ 清晰的导航链接
深入学习(专题文件)
- ✅ 完整的问题分析
- ✅ 详细的技术原理
- ✅ 常见误区和最佳实践
AI 加载效率
- ✅ 主文件更小,加载更快
- ✅ 专题文件独立,按需加载
- ✅ 减少上下文冗余
后续工作
短期(1-2 周)
- 创建专题文件
testing-event-loop-fix.md - 精简
testing.md主文件 - 更新交叉引用
- 团队内部分享测试最佳实践
- 更新其他测试文件使用新的配置
中期(1-2 月)
- 收集团队反馈,优化文档结构
- 评估是否需要创建更多专题文件
- 添加更多测试示例和模板
- 创建测试配置的快速启动脚本
长期(3-6 月)
- 建立测试质量度量体系
- 定期审查和更新测试规范
- 培训新成员测试最佳实践
- 考虑进一步拆分其他大型文档
总结
通过将 pytest 事件循环修复的最佳实践整合到项目测试规范中,并采用混合方案优化文档结构,我们:
- ✅ 建立了统一的测试标准
- ✅ 提供了完整的修复方案和原理说明
- ✅ 更新了关键代码示例
- ✅ 添加了交叉引用,易于查找
- ✅ 为团队提供了可靠的测试指南
- ✅ 优化了文档结构,提高可读性
- ✅ 创建了专题文件,便于深入学习
- ✅ 减少了主文件大小,提高 AI 加载效率
这将显著提高团队的测试质量和开发效率,避免重复遇到相同的问题,同时保持文档的可维护性和可扩展性。