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

测试最佳实践整合到 testing.md 规范

日期: 2026-02-02
类型: Documentation
影响范围: 测试规范文档、开发流程

概述

将 pytest 事件循环修复的最佳实践整合到项目测试规范文档(jointo-tech-stack skill 的 testing.md),为团队提供完整的测试指南。

背景

在完成 pytest 事件循环修复 后,需要将修复方案和最佳实践整合到项目的测试规范中,确保:

  1. 团队成员了解正确的测试配置方式
  2. 新加入的开发者可以快速上手
  3. 避免重复遇到相同的问题
  4. 建立统一的测试标准

整合方案

采用混合方案(方案 3):

  1. 创建新章节提供完整的修复方案和原理说明
  2. 更新现有章节确保代码示例是最新的
  3. 提供交叉引用,易于查找

更新内容

1. 新增章节:FastAPI + asyncpg + httpx.AsyncClient 事件循环修复

位置:在 "常用 Fixtures 使用指南" 之前

内容

  • 问题描述和错误表现
  • 根本原因分析
  • 完整修复方案(5 个步骤)
  • 修复效果对比
  • 事件循环作用域对比表
  • 相关文档链接

核心修复步骤

  1. Session 级别的事件循环
  2. Session 级别的数据库引擎
  3. Function 级别的数据库会话
  4. AsyncClient Fixture
  5. JWT Token 唯一性处理

2. 更新 "常用 Fixtures 使用指南"

更新内容

  • test_auth fixture 说明中添加 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_loop fixture
  • 使用 session 级别的 async_engine fixture
  • 使用 function 级别的 db_session fixture
  • 移除 async_clientlifespan 参数(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 秒可能不够)

文档结构

更新后的章节顺序

  1. 测试金字塔
  2. 测试目录结构
  3. FastAPI + asyncpg + httpx.AsyncClient 事件循环修复(新增)
  4. 常用 Fixtures 使用指南(更新)
  5. 集成测试认证模式
  6. API 响应格式处理
  7. 常见字段名称规范
  8. 测试类型
  9. 测试覆盖率要求
  10. 测试策略
  11. 共享 Fixtures(更新)
  12. 测试隔离原则
  13. 测试环境
  14. 最佳实践
  15. 故障排查
  16. 持续集成
  17. 相关文档
  18. 测试代码最佳实践
  19. 常见问题与解决方案(更新)
  20. 测试覆盖率要求
  21. 测试最佳实践总结
  22. 特定服务测试文档
  23. 参考资源

交叉引用

  • 新章节 → 相关 Changelog 文档
  • 常见问题 → 新章节(完整修复方案)
  • 共享 Fixtures → 新章节(原理说明)

影响范围

受益对象

  1. 新加入的开发者

    • 快速了解正确的测试配置
    • 避免常见的事件循环问题
    • 学习最佳实践
  2. 现有团队成员

    • 统一测试标准
    • 参考完整的修复方案
    • 提高测试质量
  3. 未来维护者

    • 完整的文档记录
    • 清晰的原理说明
    • 易于理解和维护

文档更新

  • .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 加载和理解效率。

优化方案

采用混合方案

  1. 创建专题文件 testing-event-loop-fix.md 存放详细内容
  2. 精简 testing.md 主文件,保留核心配置和快速参考
  3. 利用交叉引用连接主文件和专题文件

优化结果

文件大小对比

文件 优化前 优化后 变化
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 事件循环修复的最佳实践整合到项目测试规范中,并采用混合方案优化文档结构,我们:

  1. 建立了统一的测试标准
  2. 提供了完整的修复方案和原理说明
  3. 更新了关键代码示例
  4. 添加了交叉引用,易于查找
  5. 为团队提供了可靠的测试指南
  6. 优化了文档结构,提高可读性
  7. 创建了专题文件,便于深入学习
  8. 减少了主文件大小,提高 AI 加载效率

这将显著提高团队的测试质量和开发效率,避免重复遇到相同的问题,同时保持文档的可维护性和可扩展性。