pytest -n auto 能运行但非最优,真正提速关键在于测试代码适配多进程:避免资源争用、确保 fixture 隔离、合理设置并发数与分发策略,并保证每个测试独立洁净。

直接加 -n 参数就能启动并行,但多数人卡在“加了没提速”甚至“加了就失败”——根本不在 Python 版本,而在测试是否真正隔离、资源是否独占。
为什么 pytest -n auto 在 Python 3.10 下可能白跑
Python 3.10 本身对 pytest-xdist 完全兼容,无需额外适配。问题几乎都出在环境或测试代码上:
-
ImportError: No module named 'xdist':压根没装插件,只装了pytest - CI 环境(如 GitHub Actions)里
os.cpu_count()返回 1 或 2,-n auto就只起 1 个 worker,得显式写-n 2 - 测试里硬编码了端口(如
8000)、临时路径(如/tmp/cache.json)、SQLite 文件路径(如test.db),多个进程同时操作同一资源,触发PermissionError或database is locked -
@pytest.fixture(scope="session")被误认为“所有 worker 共享一份”,实际是每个 worker 各执行一次 —— 若它初始化了全局 DB 连接或写文件,就会冲突
哪些 fixture 必须改才能在 Python 3.10 + xdist 下稳定运行
Python 3.10 的 multiprocessing 默认用 spawn 方式(尤其 Windows/macOS),无法 pickle 不可序列化对象。以下 fixture 模式必须调整:
- 返回数据库连接、HTTP 会话、线程池等句柄 → 改为按 worker ID 构造独立实例:
worker_id = request.config.workerinput["workerid"] - 用
tempfile.mktemp()或硬编码路径 → 改用tmp_path(pathlib.Path类型,天然多进程安全) - 在
conftest.py里修改os.environ或写配置文件 → 这类非幂等操作必须移出sessionscope,或加锁/判重 - 依赖
time.time()或random.random()且未重置种子 → 并发下断言飘移,应在每个 test 开头调用random.seed(os.getpid())
--dist=load vs --dist=loadscope 怎么选
默认 --dist=load 按单个 test_* 函数分发,粒度最细但也最容易撞状态。真实项目中更常用:
-
--dist=loadscope:把同一个class或模块级scope的 fixture 和用例打包给一个 worker,适合含 DB setup/teardown 的测试类 -
--dist=loadfile:整份test_api.py分给一个 worker,避免文件内多个 test 共享setup_module导致的竞态 - 别用
--dist=each(全量复制),除非做兼容性验证;也别迷信--dist=load,若某文件有 150 个慢 API 测试,它会拖垮整个 worker
Python 3.10 下推荐的启动命令和调试技巧
不盲目信 -n auto,从保守值起步再调优:
- 本地开发机(8 核):先试
pytest -n 2 --dist=loadscope,观察 CPU/IO 占用,再逐步加到-n 4 - I/O 密集型(HTTP/DB):用
pytest -n $(($(nproc) / 2))(Linux/macOS),Windows 上手动设-n 2或-n 3 - 调试失败用例:加
--tb=short --log-level=INFO,否则多个 worker 的日志混在一起没法定位是哪个gw1报的错 - 防止单点崩溃:加
--max-worker-restart=2,避免某个 worker 因内存泄漏挂掉导致整轮测试中断
真正卡住的不是 Python 3.10 的版本兼容性,而是 fixture 是否按 worker 隔离、临时路径是否用 tmp_path、数据库是否用了 :memory: 或带 worker 后缀的文件名——这些细节漏掉一个,-n 就只是制造随机失败的开关。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











