跳转到内容

测试策略

学习目标

理解 CrewAI 如何通过 VCR 录制、pytest 插件生态和跨提供商测试策略保证代码质量,同时避免测试时调用真实 LLM API。


项目实践

VCR 录制 HTTP 交互

CrewAI 使用 vcrpy + pytest-recording 录制和回放 HTTP 交互:

  • lib/crewai/tests/cassettes/ 目录存放录制的 HTTP 交互
  • 测试运行时不再调用真实 LLM API
  • 使用 pytest.mark.vcr 装饰器标记需要录制的测试

关键优势:避免测试时调用真实 LLM API,节省成本和时间。

pytest 插件生态

CrewAI 使用了丰富的 pytest 插件:

插件用途
pytest-asyncio异步测试支持
pytest-subprocess子进程模拟
pytest-recordingVCR HTTP 录制
pytest-randomly随机化测试顺序,防止顺序依赖
pytest-timeout测试超时保护
pytest-xdist多进程并行测试
pytest-split将大测试套件分片
vcrpyHTTP 交互录制(固定版本,<7.0 会破坏测试)

测试目录结构

测试按子系统组织:

lib/crewai/tests/
├── agents/ # Agent 测试
├── crew/ # Crew 编排测试
├── events/ # 事件系统测试
├── knowledge/ # 知识系统测试
├── llms/ # LLM 提供商测试
├── mcp/ # MCP 集成测试
├── memory/ # 记忆系统测试
├── rag/ # RAG 测试
├── skills/ # Skills 测试
├── storage/ # 存储测试
├── tools/ # 工具测试
└── fixtures/ # 共享测试夹具

跨提供商测试策略

LLM 提供商测试需要验证 OpenAI/Anthropic/Gemini 等不同提供商的行为一致性:

  • 使用 pytest.mark.parametrize 对同一测试逻辑运行多个提供商
  • VCR cassette 为每个提供商录制独立的 HTTP 响应
  • 测试断言关注输出格式的一致性,而非响应内容

问题与规避

VCR cassette 过期

  • API 响应格式变化导致 cassette 回放失败
  • 对策:定期重新录制 cassette(如每季度一次),或在 API 版本升级时重新录制

pytest-randomly 导致测试不稳定

  • 随机化测试顺序可能暴露隐藏的顺序依赖
  • 对策:使用 --randomly-seed 固定种子复现失败,修复后移除种子

pytest-split 分片不均

  • 某些测试文件远大于其他文件,导致 CI 分片时间不均
  • 对策:使用 --splits + --group 按执行时间而非文件数分片

vcrpy 版本锁定

  • CrewAI 将 vcrpy 固定在 ~=25.1.0,其他版本会破坏 pytest-recording
  • 对策:不要手动升级 vcrpy 版本

设计取舍

VCR 录制 vs Mock

  • VCR 录制的优势是真实反映 API 行为,mock 可能遗漏 API 格式变化
  • 代价是 cassette 文件随 API 数量增长,需要维护
  • CrewAI 选择 VCR 录制,因为它测试的不仅是代码逻辑,还有与 API 的集成契约

pytest-xdist vs 顺序执行

  • xdist 将测试分片到多个进程并行执行,缩短 CI 时间
  • 代价是测试之间无法共享状态(如全局单例的 crewai_event_bus
  • CrewAI 的测试设计为无状态(每个测试创建独立的 Crew/Agent),适合 xdist

参考来源

  • 源码验证: pyproject.toml — dev dependencies
  • 源码验证: lib/crewai/tests/ 目录结构
  • 源码验证: lib/crewai/tests/cassettes/ — VCR 录制的 HTTP 交互