pytest-xdist并行加速需避免资源争抢,-n auto不等于最优,并非核数越多越快;应从-n 2起步测试,I/O密集型宜设为物理核数×1.5,配合--dist=loadscope等策略及fixture隔离设计。

因为 pytest-xdist 把原本串行执行的测试用例,分发到多个独立进程里并行跑——前提是测试本身不抢资源、不共享状态;否则加了 -n 反而更慢、更不稳定。
为什么 -n auto 不等于“开得越多越快”
它调用 os.cpu_count(),但物理核数 ≠ 实际并发收益点。笔记本上开满 16 个 worker,可能因磁盘 I/O 或内存带宽争抢卡在 Waiting for workers;CI 环境(如 GitHub Actions)默认只暴露 2 个逻辑核,-n auto 却可能起 4 个,直接超配。
- 推荐从
-n 2起步,逐步试到-n 4或-n 6,观察 CPU 利用率和总耗时拐点 - I/O 密集型测试(如 HTTP 请求、DB 查询)通常
-n值设为物理核数 × 1.5 更稳,而非盲目追逻辑核数 - Docker 容器需显式传
--cpus=4,否则os.cpu_count()返回 1,-n auto就只起 1 个 worker
真正拖慢并行效果的,90% 是 fixture 和资源管理问题
不是 xdist 不行,是测试代码没适配多进程环境。比如 @pytest.fixture(scope="session") 在每个 worker 中都会单独执行一次,若它初始化了一个全局 SQLite 连接或写死路径的临时文件,就会触发 sqlite3.OperationalError: database is locked 或 PermissionError: [WinError 32]。
- 所有临时路径必须用
tempfile.mkdtemp()或tmp_path(不是tmpdir),并拼入request.config.workerinput["workerid"]确保隔离 - 避免在
conftest.py的session或modulescope 里做非幂等操作,比如修改os.environ、写配置文件、启动单例服务 - 共享只读数据(如 JSON Schema)可用
scope="session",但返回值必须是dict/str等可序列化类型
--dist 参数选错,比不用 -n 还容易出错
默认 --dist=load 按 test function 均匀分发,但若某个 test_heavy.py 文件里塞了 200 个慢用例,它会被整个分给一个 worker,其他 worker 空转——负载严重不均。
立即学习“Python免费学习笔记(深入)”;
-
--dist=loadfile:整份test_xxx.py分给一个 worker,适合文件内共用 setup/teardown 的场景 -
--dist=loadscope:按 class 或 module 分组,确保同模块 fixture 不跨进程冲突,对含 DB 初始化逻辑的测试更稳 - 别用
--dist=load跑含 sleep 或外部 API 调用的测试,极易导致 worker 长时间空闲
Windows 上 spawn 启动子进程的隐藏陷阱
Windows 默认用 spawn 方式启动 worker 进程,会重新 import 主模块。如果主模块里直接 import torch 或 import cv2,就可能报 AttributeError: module '__main__' has no attribute '__file__'。
- 把 heavy import 移到具体 test function 内部,或用
if __name__ == "__main__":包裹 - 或者改用
fork(仅限 Linux/macOS),但 CI 环境大多不支持 - 更稳妥的做法:在 conftest.py 里用
pytest_configure做条件加载,避开主模块顶层 import
提速的边界很清晰:CPU 密集型测试收益最大,I/O 密集型次之,而真正卡住的往往不是怎么开进程,而是每个测试是否真的“干净”——有没有偷偷共用文件句柄、数据库连接、全局计数器或系统时间。


















