Pytest-Xdist 多机器分布式执行是如何实现的
从零拆解 pytest-xdist 的远程分布式架构,并与 Swarm 做全方位对比。
当 -n auto 不够用的时候
你大概率用过这行命令:
它让你的测试从"一个核慢慢跑"变成了"所有核一起上",速度提升肉眼可见。这是 pytest-xdist 最出名的功能——本地多进程并行。
但如果你的测试集有 5000 个用例,本机只有 8 核,跑完要 20 分钟,怎么办?最快的办法是把隔壁老王的那台 32 核机器也用上——这就是多机器分布式执行。
本文带你从源码层面拆解 pytest-xdist 是怎么做到的,以及它和另一个分布式测试框架 Swarm 的对比。

基本概念:用 -n auto 打底
在进入多机器之前,必须先搞懂单机并行——因为多机器只是在单机基础上换了一条通信链路而已。
核心设计:Worker 先全量收集,Controller 再通过整数索引告诉它"跑哪几个"。Worker 之间不需要知道彼此的存在。
远程模式:当 Worker 不在本机时
通信层:execnet + socketserver
pytest-xdist 远程通信依赖 execnet,专门为"跨进程执行 Python 代码"设计。它配合一个不到 133 行的 socketserver.py 实现远程 Worker 的启动。
启动步骤
远端机器(只需做一次):
socketserver.py兼容 Windows/Linux/macOS。Windows 上fcntl不可用时会自动降级,功能不受影响。
Controller 机器:
底层发生了什么
重要:remote_exec() 只传了 xdist/remote.py(约 437 行的 runner 框架),你的测试代码不会通过 execnet 传输。测试代码必须已经存在于远程机器的文件系统上。
单通道 vs 双通道
pytest-xdist 的 channel 是双向的:Controller 通过它发命令(runtests/shutdown/steal),Worker 通过它发事件(testreport/collectionfinish/logstart)。所有通信复用同一条 TCP 连接。
Worker 的完整生命周期
每个远程 Worker 内部跑的是一个完整但被劫持了的 pytest Session:
一个 Worker 就是一个"被远程操控的 pytest"。它正常收集所有测试、正常创建 Session,只是
runtestloop被 WorkerInteractor 劫持——不再自动遍历session.items,而是从 Controller 的指令队列torun里取索引。
五大调度策略
pytest-xdist 提供了 5 种调度器,通过 --dist 参数选择:
LoadScheduling:朴素的负载均衡
low-watermark 机制:Worker 待执行数低于阈值自动补充。如果单个测试慢(>0.1秒),减少补充量避免堆积:
WorkStealingScheduling:快的别闲着
场景:Worker1 分到了很多慢测试,Worker2 全是快测试,Worker2 跑完在发呆。
steal 的原子性保证:
"要么全偷,要么一个不偷"——工作窃取的艺术。

LoadScopeScheduling:别反复创建 fixture
当你有 session 级别的 fixture(比如数据库连接),不同 Worker 反复创建/销毁非常浪费。loadscope 让共享 fixture 的测试尽可能留在同一个 Worker。
同一个 scope 下的所有测试绑定到同一个 Worker,Worker 的 Session 内 module/class scoped fixture 只创建一次。
崩溃恢复

默认最大重启次数 = Worker数量 × 4,可通过 --max-worker-restart 调整。
多机器实操步骤
-n 0表示 Controller 本地不跑测试,全部发给远程 Worker。
pytest-xdist vs Swarm
Swarm 是另一个分布式测试框架,采用 Server-Client 架构。下面从纯多机器远程测试的角度做对比。
连接模型
分发粒度——最根本的差异
假设 3 台机器,3 个文件。test_slow.py 100 个慢用例(每用例 5s),另外两个文件各 100 个快用例:
环境管理
故障恢复
报告与可观测性
综合定位
选型指南
结语
pytest-xdist 的远程分布式模式本质上是一个设计精巧的"远程遥控"系统。它没有试图成为一个完整的测试平台,而是在 pytest 已有的生态里,用最小改动(一个 plugin、一个 remote 模块、一个 socket server)实现了多机器的协作。
它的核心哲学:Worker 不需要知道全局,只需要知道自己的 session.items 列表和 Controller 让它跑的第几个。Controller 负责所有协调。
Codebase 小(核心约 2200 行)、概念少(Controller + Worker + Channel)、和 pytest 生态零摩擦。代价是运维工作压在你身上——每台机器都要手动 clone、装依赖、起 socketserver。
Swarm 走了另一条路:用独立服务接管运维,但牺牲了调度精细度。没有谁绝对更好——场景说了算。
如果你想兼得两者优势:Swarm 分发文件到客户端 + 客户端内部用 pytest -n auto——这是目前最接近"理想"的方案。
文中源码引用基于 pytest-xdist v3.8.0 和 Swarm v0.1.0。