ROMA-DSPy 测试体系实战指南:基于 pytest 标记的分层测试架构与运行策略
【免费下载链接】ROMARecursive-Open-Meta-Agent v0.1 (Beta). A meta-agent framework to build high-performance multi-agent systems.项目地址: https://gitcode.com/GitHub_Trending/roma7/ROMA
导读
tests/README.md是 ROMA-DSPy(Recursive-Open-Meta-Agent v0.1)官方测试套件的使用手册,定义了从单元测试到端到端测试的完整分层体系,以及一套以 pytest 标记(marker)驱动的灵活执行策略。本文以该文档为主体,结合仓库中 pytest.ini、tests/conftest.py、tests/fixtures/test_fixtures.py 等真实配置与源码,系统讲解测试目录组织、标记体系、环境搭建、测试编写规范与排障方法,帮助你在一台机器上同时驾驭"秒级单测"与"依赖 PostgreSQL / LLM / E2B 的完整集成验证"。
一、测试目录组织:按依赖层级划分的四层结构
tests/目录按照"测试速度与外部依赖程度"进行组织,从纯内存单元测试到真实服务端到端测试逐层递进:
tests/ ├── unit/ # Fast, isolated unit tests ├── integration/ # Integration tests with external services ├── tools/ # Toolkit-specific tests ├── validation/ # Validation and verification tests ├── performance/ # Performance benchmarks (future) └── fixtures/ # Shared test fixtures各目录定位如下:
- unit/:快速、隔离的单元测试,无任何外部依赖(网络、数据库、LLM API 均不触碰)。例如 tests/unit/test_dag_serialization.py 验证 DAG 的序列化/反序列化,tests/unit/test_checkpoint_manager.py 验证 CheckpointManager 的创建、加载、轮换与完整性校验。
- integration/:与外部服务交互的集成测试,如 PostgreSQL 持久化(test_e2e_postgres_persistence.py)、API 端点(test_api_endpoints.py)、检查点与缓存协同(test_cache_checkpoint_synergy.py)。
- tools/:针对具体 Toolkit 的测试,如 test_binance_e2e.py、test_coingecko_integration.py。
- validation/:关键路径的验证与校验测试,如 tests/validation/test_integration_flow.py 检查 ConfigManager → FileStorage → ContextManager → Toolkit 的完整调用链。
- performance/:预留的性能基准目录(当前处于规划阶段,见下文"性能测试"一节)。
- fixtures/:共享测试夹具与可复用测试数据,核心实现在 tests/fixtures/test_fixtures.py。
从仓库实际的目录结构看,performance/目录尚未创建,文档中明确标注其为"future";这也与 README 中"Performance tests are planned for future development"的描述一致。
二、测试标记体系:用 marker 精确控制测试子集
所有测试通过 pytest 标记(marker)分类,标记定义集中注册在 pytest.ini 的markers段。由于pytest.ini开启了--strict-markers,任何未注册的标记都会直接报错,因此以下标记清单即仓库中"唯一合法"的标记全集。
2.1 主要类别(Primary Categories)
| 标记 | 含义 |
|---|---|
unit | 快速单元测试,无外部依赖 |
integration | 需要外部服务的集成测试 |
e2e | 端到端系统测试 |
2.2 依赖要求标记(Requirement Markers)
| 标记 | 含义 |
|---|---|
requires_db | 需要 PostgreSQL 数据库(从 pytest.ini 的注释看,也常与requires_llm组合出现在持久化端到端测试中) |
requires_llm | 需要 LLM API Key(OpenAI 等) |
requires_e2b | 需要 E2B 沙箱环境 |
2.3 功能标记(Feature Markers)
| 标记 | 含义 |
|---|---|
checkpoint | 检查点/恢复功能测试 |
error_handling | 错误传播与处理测试 |
tools | Toolkit 集成测试(加密、Web 搜索等) |
performance | 性能基准测试 |
slow | 长时间运行的测试 |
此外 pytest.ini 还额外注册了network(需要网络访问,通常被 mock)、file_io(执行文件 I/O 操作)、e2b(E2B 沙箱集成测试)等标记,供测试作者按需使用。
2.4 严格的 pytest 基线配置
pytest.ini 为整个测试套件固化了以下行为:
- 测试发现规则:
testpaths = tests,python_files = test_*.py,python_classes = Test*,python_functions = test_*; - 默认
addopts:--verbose --tb=short --strict-markers --strict-config --disable-warnings --color=yes --durations=10,其中--durations=10会在每次运行后输出最慢的 10 个测试,便于持续定位性能热点; - 异步测试:
asyncio_mode = auto,结合pytest-asyncio可让async def test_*直接被识别为异步测试; - 默认忽略
DeprecationWarning、PendingDeprecationWarning与未关闭资源的ResourceWarning; - 最小 Python 版本
minversion = 3.8(注意:仓库pyproject.toml中requires-python = ">=3.12",实际运行时以 3.12+ 为准); - 注释中预留了
--numprocesses=auto(pytest-xdist)用于并行执行,需要时取消注释即可启用。
三、运行测试:从全量回归到精准定向
3.1 全量运行
pytest等价于pytest tests/(见 justfile 中的test任务)。
3.2 按标记筛选
# 只跑快速单元测试 pytest -m unit # 跑集成测试(需外部服务) pytest -m integration # 只跑检查点相关测试 pytest -m checkpoint # 只跑 Toolkit 测试 pytest -m tools标记还支持布尔表达式组合,这在"跳过重依赖、跑轻量子集"的场景中非常实用:
# 不需要数据库的集成测试 pytest -m "integration and not requires_db" # 需要数据库 + LLM 的完整端到端测试 pytest -m "e2e and requires_db and requires_llm"3.3 按目录 / 文件 / 函数定位
# 所有单元测试 pytest tests/unit/ # 单个测试文件 pytest tests/unit/test_dag_serialization.py # 单个测试函数(node id 定位) pytest tests/unit/test_dag_serialization.py::test_serialize_task_node3.4 覆盖率报告
# 生成 HTML 覆盖率报告 pytest --cov=src/roma_dspy --cov-report=html # 打开报告(macOS/Linux) open htmlcov/index.htmlpytest.ini 中还注释了阈值示例:pytest --cov=src/roma_dspy --cov-report=html --cov-fail-under=85可将覆盖率门槛设为 85%,低于阈值即失败。justfile 的test-coverage任务则同时输出 term 与 html 两种报告。
3.5 超时控制
针对慢测试,可借助pytest-timeout插件设置全局超时:
pytest --timeout=300四、搭建测试环境:四步走
4.1 安装开发依赖
pip install -e ".[dev]"[dev]组在 pyproject.toml 中定义了完整的测试工具链:pytest>=8.4.2、pytest-asyncio>=1.2.0、pytest-mock>=3.15.1、pytest-loguru>=0.4.0、pytest-cov>=4.0.0,外加ipython、ipdb、ruff、mypy等开发与质量工具。
4.2 启动 PostgreSQL(数据库相关测试)
docker-compose up -d postgres # 验证运行状态 docker-compose ps # 查看日志 docker-compose logs postgres仓库根目录的 docker-compose.yaml 定义了postgres服务:使用postgres:16-alpine镜像,数据库名、用户名、密码与端口均支持环境变量覆盖(默认roma_dspy/postgres/postgres/5432),并内置了pg_isready健康检查(间隔 5s、重试 5 次)。该 compose 文件还一并编排了 MinIO(S3 对象存储)、roma-api 与可选的 MLflow(--profile observability时启用)。
4.3 配置环境变量
# LLM 测试所需 export OPENAI_API_KEY=sk-... export FIREWORKS_API_KEY=... # 数据库测试所需(与 docker-compose 默认值一致) export DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost/roma_dspy_test # 可选:E2B 沙箱 export E2B_API_KEY=...4.4 首次运行数据库迁移
uv run alembic upgrade head迁移文件位于 src/roma_dspy/core/storage/alembic/versions/,包含初始 schema、事件追踪表、DAG 快照迁移、Toolkit 指标表、experiment 名称等 8 个版本,可追溯数据库结构演进历史。集成测试 tests/integration/test_e2e_postgres_persistence.py 中会动态写入一份内嵌的 YAML 配置(含storage.postgres.enabled: true与连接串),并通过ConfigManager加载,展示了从配置到存储的完整链路。
五、编写测试:结构、标记与夹具
5.1 基本测试结构
import pytest @pytest.mark.unit def test_my_unit_test(): """Test description.""" # Fast test with no external dependencies assert True @pytest.mark.integration @pytest.mark.requires_db async def test_my_integration_test(postgres_storage): """Test description.""" # Integration test using fixtures result = await postgres_storage.get_execution("exec_123") assert result is not None第二个示例中postgres_storage夹具来自 conftest,且由于asyncio_mode = auto,async def测试无需额外装饰器即可被 pytest-asyncio 驱动。
5.2 多标记与条件跳过
# 单个标记 @pytest.mark.unit # 多个标记 @pytest.mark.integration @pytest.mark.slow @pytest.mark.requires_db # 带条件跳过 @pytest.mark.skipif( not os.getenv("OPENAI_API_KEY"), reason="Requires OPENAI_API_KEY environment variable" )5.3 共享夹具(Fixtures)
文档指出公共夹具集中在tests/conftest.py与tests/fixtures/,核心包括:
postgres_storage—— 已初始化的PostgresStorage实例;postgres_config—— 用于测试的PostgresConfig;temp_checkpoint_dir—— 检查点测试专用临时目录;- 针对 LLM 与外部服务的 Mock 夹具。
值得深入阅读的三类夹具源码
(1)LLM 无网络化:stub_prediction_strategy(conftest 中的 autouse 夹具)
conftest.py 通过monkeypatch替换PredictionStrategy.build,使所有预测都走一个DummyPredictor,按签名名(AtomizerSignature、PlannerSignature、ExecutorSignature、AggregatorResult、VerifierSignature)返回确定性伪结果——例如原子化任务返回is_atomic决策、规划器返回两条带依赖的子任务、验证器根据输出中是否含"fail"给出裁决。这让"原子化 → 规划 → 执行 → 聚合 → 验证"的完整流程可以在不发起任何 LLM 调用的前提下被端到端驱动,是"单元测试快速、无外部依赖"这一原则的直接实现。
(2)API 测试:test_app/client
mock_storage、mock_config_manager、mock_execution_service三个夹具用AsyncMock/MagicMock构造了 FastAPI 应用的全部依赖,test_app以create_app(enable_rate_limit=False)创建应用并注入模拟的 app state,client则通过httpx.AsyncClient + ASGITransport提供异步 HTTP 客户端——无需真正启动 uvicorn 即可测试 src/roma_dspy/api 的路由逻辑。
(3)领域级构造工厂:tests/fixtures/test_fixtures.py
test_fixtures.py 提供了四组可复用构造器:
MockModuleFactory—— 一键生成 Atomizer/Planner/Executor/Aggregator 的 Mock 组合,可配置原子化决策、子任务列表、执行失败等场景;TaskNodeFactory—— 生成简单/已完成/失败任务节点,以及含 4 个节点与 3 层深度的层级任务树;DAGFactory—— 基于 src/roma_dspy/core/engine/dag.py 的TaskDAG创建简单、层级、含失败节点的 DAG;ConfigurationFactory—— 提供测试型、极简型、生产仿真型三种CheckpointConfig(对应max_checkpoints、max_age_hours、compress_checkpoints、verify_integrity、cleanup_interval_minutes等参数的不同组合)。
辅助工具方面,TestErrorSimulator可批量生成网络/校验/资源类错误并模拟"失败 N 次后成功"的间歇性故障,TestAssertionHelpers封装了检查点有效性、任务状态、错误上下文增强等高频断言。
六、连续集成(CI)策略
README 明确了自动化触发规则:
- Pull Request:运行单元测试 + 无外部依赖的集成测试;
- Main 分支提交:运行包含外部服务的完整测试套件。
注:README 引用
.github/workflows/ci.yml作为 CI 配置入口,但当前仓库快照中未包含.github目录,说明 CI 配置可能尚未提交到该仓库或位于私有托管侧。读者在自有仓库落地时,可参照上述 PR/主干分级策略自行配置等价流水线。
七、排障指南
7.1 测试超时
# 为慢测试提高超时阈值 pytest --timeout=3007.2 数据库连接错误
# 确认 Postgres 正在运行 docker-compose ps # 彻底重置数据库(删除卷后重建) docker-compose down -v docker-compose up -d postgres7.3 导入错误
# 以可编辑模式重装 pip install -e .若使用uv,对应命令为uv pip install -e ".[dev]"(仓库多处命令基于 uv 执行,如uv run alembic upgrade head)。
7.4 测试被跳过
# 查看跳过原因(-rs 显示每个 skip/xfail 的理由) pytest -v -rs # 强制执行 xfail 测试(危险操作,慎用) pytest --runxfail7.5 常见跳过根因
大部分跳过源于环境变量缺失:requires_llm测试在未设置OPENAI_API_KEY时会被跳过;requires_db测试在数据库未启动或DATABASE_URL未指向可用实例时被跳过;requires_e2b测试则需要E2B_API_KEY。仓库中亦有先例:tests/validation/test_integration_flow.py 因 ToolkitFactory 被合并进 ToolkitManager 的 API 重构,整模块以pytest.mark.skip(reason="...")跳过并在文档字符串中记录了迁移说明——这本身也是一种值得借鉴的"重构期间冻结验证路径"的做法。
八、测试最佳实践
- 保持单元测试快速—— 不触碰 I/O、网络与外部服务;conftest 的
stub_prediction_strategy正是让"无 LLM 也能跑全流程"的典型手段; - 准确使用标记—— 为每个测试贴上与其依赖程度匹配的标记,才能发挥
-m定向筛选的价值; - Mock 外部依赖—— 单元测试中的 LLM 一律使用 Mock(DummyPredictor、AsyncMock 等),避免网络抖动导致的不稳定;
- 用夹具管理资源—— 通过 fixture 完成 setup/teardown,如
temp_checkpoint_storage、clean_loguru(清理 loguru handlers)、caplog_loguru(将 loguru 日志桥接到 pytest caplog)等; - 覆盖边界条件—— 非法输入、错误条件、边界值;
TestErrorSimulator提供的网络/校验/资源错误枚举可直接用于此类用例; - 用 docstring 说明测试意图—— 让每个测试"为什么存在"一目了然,也便于后续维护者与 LLM 检索。
九、性能测试与测试数据
9.1 性能测试(规划中)
README 明确性能基准测试处于未来规划阶段,预留了命令形态:
# 运行性能基准(future) pytest -m performance --benchmark-only该命令依赖pytest-benchmark插件与performance标记,当前仓库尚未落地,属前瞻性约定。
9.2 测试数据管理
- 可复用测试数据位于
tests/fixtures/(如TaskNodeFactory、DAGFactory生成的结构化数据); - 测试特有数据放在各测试文件内部;
- 严禁将 API Key、凭据等敏感数据提交进测试文件——这既是安全红线,也是防止 CI 泄露与外部服务被滥用(如 LLM 额度消耗)的基本要求。
十、小结:一套可复制的"分层 + 标记"测试方法论
tests/README.md为 ROMA-DSPy 定义了清晰的测试演进路径:目录按依赖层级物理分层,标记按"类别 / 依赖 / 功能"三维语义化分类,pytest.ini以--strict-markers保障标记纪律,conftest.py与test_fixtures.py通过 autouse 预测桩与领域构造工厂大幅降低测试成本。这套体系既保证了日常开发中"秒级单元回归"的即时反馈,又为数据库、LLM、E2B 等重依赖场景保留了按需开启的精确通道——无论你是要为本仓库贡献测试,还是要为其他多 Agent 框架搭建类似的质量防线,这份文档与其背后的实现都值得逐行研读。
【免费下载链接】ROMARecursive-Open-Meta-Agent v0.1 (Beta). A meta-agent framework to build high-performance multi-agent systems.项目地址: https://gitcode.com/GitHub_Trending/roma7/ROMA
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考