AReaL 单元测试规范全解从 Pytest 标记、GPU 跳过策略到 torchrun 分布式测试的落地实践【免费下载链接】AReaLThe RL Bridge for LLM-based Agent Applications. Made Simple Flexible.项目地址: https://gitcode.com/GitHub_Trending/are/AReaL在 AReaL面向 LLM Agent 应用的 RL 训练框架中为新增的 dataset loader、workflow、reward 函数或引擎模块补充测试时必须遵循项目既定的测试约定否则测试既无法被 CI 正确调度也可能在 GPU 缺失的环境里失败。本文基于仓库中的 add-unit-tests 技能文档 及其配套的 testing.md 规则文件完整梳理 AReaL 的两类测试划分、文件命名、pytest 标记与 CI 策略、分布式环境 mock、GPU 依赖处理与断言规范并结合 pyproject.toml 中的 marker 注册和 CI 工作流的真实执行命令给出可验证的落地依据读完即可按仓库惯例写出可直接运行的 AReaL 测试。一、AReaL 的两类测试单元测试与分布式测试AReaL 的测试体系分为两大类其定位、存放位置和运行方式各不相同测试类型目的位置模式运行方式单元测试测试单个函数/模块tests/test_module_feature.py直接通过 pytest 运行分布式测试测试分布式/并行行为tests/torchrun/run_*.py通过 torchrun 启动由 pytest 测试文件以子进程方式调用需要强调的一点是所有测试最终都通过 pytest 触发。分布式测试虽然依赖torchrun多进程启动但入口仍然是一个 pytest 测试函数——它在内部用subprocess拉起torchrun再校验子进程的输出。仓库中tests/torchrun/目录下存放的就是一批这样的多进程入口脚本例如 run_fsdp_dcp_distributed.py、run_fsdp_ulysses_train_batch.py、run_vocab_parallel.py 等。一个真实的调用链样例来自 tests/test_fsdp_dcp.pypytest 测试函数_run_test_with_torchrun先通过areal.utils.network.find_free_ports找空闲端口再执行subprocess.run( [ torchrun, f--nproc_per_node{n_gpus}, --nnodes1, --master-addrlocalhost, f--master_port{port}, tests/torchrun/run_fsdp_dcp_distributed.py, f--backend{alloc_mode}, f--output{output}, f--test_type{test_type}, ], checkTrue, capture_outputTrue, textTrue, )随后读取torchrun子进程写出的结果文件并断言其内容为Passed。该模式值得注意的两个细节进程数由ModelAllocation.from_str(alloc_mode).parallel.world_size从 AReaL 自己的资源分配串如fsdp:d2t1c1推导而来而不是硬编码失败时通过pytest.fail把子进程的 stderr/stdout 一并带回方便定位多进程崩溃。二、测试文件的命名与导入约定新建测试文件时遵循test_module_feature.py的命名约定即test_*.py这是 pytest 默认收集模式也能命中的形态文件放在 tests 根目录或其子目录tests/experimental/、tests/infra/、tests/v2/等这些目录都会被 CI 收集见第五节。技能文档给出的标准文件骨架如下import pytest import torch # Import the module to test from areal.dataset.gsm8k import get_gsm8k_sft_dataset from tests.utils import get_dataset_path # Optional test utilities # For mocking tokenizer: from unittest.mock import MagicMock其中tests.utils是仓库提供的轻量转发层tests/utils.py 仅从 areal/utils/testing_utils.py 重新导出get_dataset_path与get_model_path两个工具函数。这两个函数的实现遵循本地优先、Hub 兜底策略——若local_path存在则直接返回否则调用huggingface_hub.snapshot_download下载数据集/模型areal/utils/testing_utils.py#L27-L85并在模型下载时通过ignore_patterns跳过.gguf/.ggml等大文件以加速测试。同文件还维护了DENSE_MODEL_PATHS与MOE_MODEL_PATHS两组懒解析_LazyModelPaths的模型路径表供需要真实小模型的测试按model_type取用。三、测试函数Arrange-Act-Assert 与命名规范每个测试函数遵循 Arrange-Act-Assert 三段式结构并附描述性 docstringdef test_function_under_condition_returns_expected(): Test that function returns expected value under condition. # Arrange input_data 5 expected_output 10 # Act result function_under_test(input_data) # Assert assert result expected_output测试函数命名遵循test_what_condition_expected模式。仓库中的实际用例可以印证这一风格例如 tests/test_utils.py 中的test_align_mb_list_sequences_does_not_add_batch_row其 docstring 明确陈述了被验证的不变量BSHD sequence alignment must preserve the number of real batch rows并且该文件大量使用pytest.mark.parametrize对seq_lens、seq_align_to等组合做参数化覆盖是参数化测试 清晰命名的参考实现。四、Pytest 标记体系与 CI 调度策略4.1 常用标记技能文档列出的核心标记如下Marker适用场景pytest.mark.slow耗时超过 10 秒的测试默认不进入 CIpytest.mark.ci慢但必须进 CI 的测试需与pytest.mark.slow叠加使用pytest.mark.asyncio异步测试函数pytest.mark.skipif(cond, reason...)条件跳过pytest.mark.parametrize(...)参数化测试这里有一处需要注意的口径差异技能文档表述slow阈值为超过 10 秒而 pyproject.toml 中markers注册项的官方描述是 expected to cost more than 30 seconds and will not run in CI by default。从源码结构看pyproject.toml才是 pytest 实际读取的注册源编写测试时应以它为准耗时明显超过 30 秒的测试才打slow标记。4.2 pyproject.toml 中注册的完整标记集pyproject.toml 的[tool.pytest.ini_options]不仅注册了技能文档提到的标记还额外定义了面向后端与硬件环境的标记这些标记在 AReaL 的测试中同样高频出现markers [ slow: mark test as slow, expected to cost more than 30 seconds and will not run in CI by default., ci: mark test as must-run in CI (only marked for slow tests)., gpu: mark test that uses a single GPU, multi_gpu: mark test that uses more than one GPU, sglang: mark test that requires the SGLang inference backend, vllm: mark test that requires the vLLM inference backend, integration: requires external services or credentials, ]gpu/multi_gpu标记需要单卡或多卡的测试例如 tests/test_fsdp_dcp.py 中两个分布式用例同时打了pytest.mark.multi_gpu与pytest.mark.slowsglang/vllm标记依赖特定推理后端的测试例如 tests/test_fsdp_engine_nccl.py 在模块级用pytestmark pytest.mark.sglang统一打标——这正是 CI 双矩阵按后端排除的基础见 4.3integration标记需要外部服务或凭据的测试。同一段配置还设置了pythonpath [.]保证tests与areal包在仓库根目录下均可导入以及一批filterwarnings规则忽略 torch/transformers 的弃用与用户警告保证测试输出干净。4.3 CI 的真实调度表达式CI 策略在技能文档中的抽象描述是slow默认排除、slowci强制保留而 test-areal.yml 工作流中真实的执行命令是pytest -m (not slow or ci) and not ${EXCLUDE_BACKEND} --durations20 -s -vv tests/test_*.py tests/experimental/ tests/infra/ tests/v2/这条命令揭示了技能文档没有展开的三个实操要点表达式(not slow or ci) and not ${EXCLUDE_BACKEND}EXCLUDE_BACKEND由矩阵变量决定sglang 变体排除vllm标记vllm 变体排除sglang标记即 CI 会在两套推理后端上各跑一遍全量测试收集范围显式覆盖tests/test_*.py、tests/experimental/、tests/infra/、tests/v2/四个位置新写测试只要落在这几处即可被自动收集--durations20会在结束时打印最慢的 20 个用例方便发现需要补slow标记的耗时测试。标记的完整用法示例继承自技能文档pytest.mark.asyncio async def test_async_function(): result await async_function() assert result expected pytest.mark.skipif(not torch.cuda.is_available(), reasonCUDA not available) def test_gpu_feature(): tensor torch.tensor([1, 2, 3], devicecuda) # ... assertions pytest.mark.parametrize(batch_size, [1, 4, 16]) def test_with_parameters(batch_size): # Parameterized test pytest.mark.slow def test_slow_function(): # Excluded from CI by default pytest.mark.slow pytest.mark.ci def test_slow_but_required_in_ci(): # Slow but must run in CI五、Mock 分布式环境单测边界在哪里对于需要在单元测试中假装存在分布式进程组的场景技能文档给出的标准做法是用monkeypatch显式替换torch.distributed的 rank/world_size 查询import torch.distributed as dist def test_distributed_function(monkeypatch): monkeypatch.setattr(dist, get_rank, lambda: 0) monkeypatch.setattr(dist, get_world_size, lambda: 2) result distributed_function() assert result expected配套的三条纪律与 testing.md 一致单元测试中如需进程组使用torch.distributed.fake_pg这类 fake 实现显式 mockdist.get_rank()和dist.get_world_size()而不是构造真实进程组不要 mock FSDP/DTensor 的内部实现——这类行为的正确性应交给集成/分布式测试即第二节的tests/torchrun/run_*.py通道来验证。这条边界划分与仓库实践完全吻合纯 rank 逻辑的验证走单测 mock而 NCCL 通信、权重广播等真实多卡行为则由tests/test_fsdp_engine_nccl.py这类文件拉起 SGLang 服务与多卡环境来覆盖。六、GPU 依赖处理优雅跳过 显存清理GPU 测试的三条硬性约束GPU 不可用时永远优雅跳过而不是失败CUDA_AVAILABLE torch.cuda.is_available() pytest.mark.skipif(not CUDA_AVAILABLE, reasonCUDA not available) def test_gpu_function(): tensor torch.tensor([1, 2, 3], devicecuda) # ... assertions仓库中分布式测试则惯用运行时探测例如 tests/test_fsdp_dcp.py 在函数体内先判断current_platform.device_count() 2即pytest.skip(Distributed test requires 2 GPUs to run)——模块级skipif管有没有 GPU函数体内基于device_count的探测管够不够多卡两者常配合使用。清理显存在 fixture 中调用torch.cuda.empty_cache()释放 GPU 内存避免用例之间相互污染用最小的模型/批大小单元测试应只取最小可行规模配合areal.utils.testing_utils中 Qwen 0.5B/0.6B 这类 dense 小模型路径。此外 tests/conftest.py 提供了一个会话级ip_stackfixture通过探测 IPv4/IPv6 回环与外网路由能力返回{ipv4_loopback, ipv6_loopback, ipv4_route, ipv6_route}四个布尔量——编写涉及网络通信的分布式测试时可先消费该 fixture 判断环境再决定跳过或运行。七、断言规范拒绝裸tensor.equal()张量比较的统一要求使用torch.testing.assert_close()做张量比较而不是assert tensor.equal()——后者在失败时不会给出任何数值差异信息数值敏感测试必须显式指定rtol/atol把容差意图写进断言本身优先使用tmp_pathfixture 而非手工临时目录用monkeypatch注入环境变量昂贵 fixture 按session module function从大到小选择作用域。八、参考实现索引技能文档列出的三个参考文件及其可借鉴模式均已确认存在于仓库测试文件说明关键模式tests/test_utils.py工具函数测试fixtures、参数化测试、不变量 docstringtests/test_examples.py带数据集加载的集成测试数据集路径解析、成功模式匹配tests/test_fsdp_engine_nccl.py分布式测试模块级pytestmark、torchrun 集成、真实 SGLang 服务 fixture九、常见错误清单技能文档总结的七类高频错误编写 AReaL 测试时可逐条对照自查测试文件未纳入收集确认文件名符合test_*.py且位于 CI 收集的目录tests/、tests/experimental/、tests/infra/、tests/v2/GPU 依赖未加跳过所有 GPU 测试必须有pytest.mark.skipif或运行时pytest.skip张量比较方式错误用torch.testing.assert_close()不要用assert tensor.equal()GPU 测试内存泄漏fixture 中torch.cuda.empty_cache()清理过度 mock不要 mock FSDP/DTensor 内部实现命名不清晰遵循test_what_condition_expected缺少 docstring每个测试函数都应说明它验证的不变量。十、运行测试的标准命令# First check GPU availability (many tests require GPU) python -c import torch; print(GPU available:, torch.cuda.is_available()) # Run specific test file uv run pytest tests/test_name.py # Skip slow tests (CI default) uv run pytest -m not slow # Run with verbose output uv run pytest -v # Run distributed tests (requires torchrun and multi-GPU) # Note: Usually invoked via pytest test files torchrun --nproc_per_node2 tests/torchrun/run_test.py注意本地调试 CI 全量行为时建议直接复现第四节的完整表达式如uv run pytest -m (not slow or ci) and not vllm --durations20 -s -vv tests/test_*.py这样标记排除、后端排除与目录范围都与 CI 保持一致。十一、与其他开发流程的衔接技能文档将本测试流程定位为若干开发技能的后置环节新增 dataset 之后/add-dataset为新 dataset loader 补测试可复用get_dataset_path的本地优先/Hub 兜底路径解析新增 workflow 之后/add-workflow为新 rollout workflow 补测试areal.utils.testing_utils.TestWorkflow提供了一个最小可运行的随机数据 workflow 实现可作为参照新增 reward 之后/add-reward为新 reward 函数补测试。小结AReaL 的测试体系可以概括为一条主线pytest 是唯一入口命名与 marker 决定 CI 行为mock 边界决定测试层次。具体而言——文件命名test_module_feature.py决定收集slow/ci/gpu/multi_gpu/sglang/vllm/integration七类注册标记pyproject.toml与 CI 表达式(not slow or ci) and not ${EXCLUDE_BACKEND}test-areal.yml共同决定调度monkeypatchmock rank/world_size 与不 mock FSDP/DTensor 内部的纪律划出单测与分布式测试的边界而torchrun子进程 输出文件断言Passed的模式tests/test_fsdp_dcp.py则是多卡行为验证的标准落地方式。按此约定编写测试新功能的覆盖即可无缝融入 AReaL 的 CI 矩阵。【免费下载链接】AReaLThe RL Bridge for LLM-based Agent Applications. Made Simple Flexible.项目地址: https://gitcode.com/GitHub_Trending/are/AReaL创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
