Pytest插件之pytest-xdist分布式执行用例
一、背景
在现代软件开发中,自动化测试是保证代码质量的关键一环。随着项目规模的扩大,测试套件的执行时间可能变得难以接受。pytest-xdist
是一个 Pytest 插件,它通过并发和分布式测试执行,帮助我们显著提高测试效率。
pytest-xdist
是一个 Pytest 插件,用于并行执行测试,支持跨多个CPU核心或通过网络跨多台机器运行测试。这使得我们可以在更短的时间内完成更多的测试,加速CI/CD流程。
二、安装
- 确保已经安装了pytest。如果没有安装,可以使用以下命令进行安装:
pip install pytest
- 安装pytest-xdist插件:
pip install pytest-xdist
三、原理
pytest-xdist的工作原理主要包括以下几个方面:
- 解析命令行参数:pytest-xdist会解析命令行参数,获取用户指定的分发模式、进程数、主机列表等信息。
- 加载测试用例:pytest-xdist会加载所有的pytest测试用例,并将它们分发给多个进程或计算机上的worker进行并行执行。
- 分布式执行:pytest-xdist不仅可以在多进程上运行测试,还可以在多个计算机上进行分布式测试。每个进程或worker负责执行一部分测试用例,从而大大提高测试效率
四、命令行参数说明
1、参数说明
pytest-xdist
提供了多个命令行参数来控制并发和分布式测试的行为:
-n
或--numprocesses
:设置并发执行的进程数或线程数。--dist
:定义并发模式,可选的模式有loadscope
,loadfile
,loadclass
,loadmodule
,each
。--tx
或--executor
:定义使用的执行器,如thread
,process
,forked
,subprocess
。
2、分发策略
pytest-xdist提供了多种参数来控制测试的执行方式和分发策略:
--dist=loadscope
将按照同一个作用域方法来分组(按照模块或类进行分组),然后将每个测试组发给可以执行的worker,确保同一个组的测试用例在同一个进程中执行--dist=loadfile
按照同一个文件名来分组,然后将每个测试组发给可以执行的worker,确保同一个组的测试用例在同一个进程中执行--dist=each
是将每个用例,分别发给所有的执行器worker,相当于开了几个执行器worker,同一个用例就执行几遍--dist=load
默认选项,将待运行的用例随机发给可用的执行器worker,不保证执行顺序。
3、执行器
--tx
(或其等价的 --executor
)参数让你定义使用的执行器类型。这个参数对于决定如何在不同的工作进程中执行测试非常有用。
-
thread:在每个工作进程中使用线程执行测试。
-
process:在每个工作进程中使用多个进程执行测试(默认)。
-
forked:在每个工作进程中使用
multiprocessing
模块的Process
类。 -
subprocess:在子进程中执行测试,适用于某些需要隔离环境的场景。
-
示例:使用线程执行器。
pytest --tx=thread
4、命令用法
-
使用4个进程并发执行测试:
pytest -n 4
-
每个测试文件作为一个独立的进程执行:
pytest -n auto --dist=loadfile
-
使用远程执行器(例如,使用
pytest-xdist
的subprocess
模式):pytest --tx subprocess
五、配置文件参数
除了命令行参数,pytest-xdist
的配置也可以在 pytest.ini
或 pyproject.toml
文件中设置:
1、pytest.ini 示例
[pytest]
addopts = -n 4 --dist=loadfile
2、pyproject.toml 示例
[tool.pytest]
addopts = "--n 4 --dist=loadfile"
3、参考示例代码
假设我们有以下测试文件结构:
/tests
|-- test_example1.py
|-- test_example2.py
每个文件包含若干独立的测试用例。
test_example1.py 示例代码
def test_example1_pass():
assert 1 == 1
def test_example1_fail():
assert 1 == 2
test_example2.py 示例代码
import time
def test_example2_slow():
time.sleep(2)
assert True
要并发执行这些测试,你可以使用以下命令:
复制
pytest -n 4
六、使用场景
1、如何保持session执行一次
当使用 pytest-xdist
插件进行并行测试执行时,确保 scope="session"
的 fixture 只执行一次可能会有些复杂。
由于并行执行的本性,多个进程可能同时尝试初始化这样的 fixture。为了处理这个问题,你可以采取以下策略:使用 Global Lock
使用文件锁或分布式锁来确保初始化操作只在一个进程中执行一次。
import pytest
from filelock import FileLock
lock = FileLock("test_init.lock")
def initialize():
with lock:
# 放置你的初始化代码,例如登录
# 确保这里的代码是幂等的
# web ui自动化,声明一个driver,再返回driver
# 或者接口自动化,先执行登录请求操作,再返回token
pass
@pytest.fixture(scope="session")
def test_setup():
initialize()
# 其他 setup 逻辑
yield
2、ssh和socket远程通信
pytest-xdist
插件确实支持通过网络进行分布式测试执行,它可以通过 SSH (Secure Shell) 或者 socket 来进行 master 和 worker 之间的远程通信。以下是如何使用这两种方式实现远程通信的基本步骤:
- 使用 SSH 进行远程通信
-
安装 paramiko:
pytest-xdist
使用paramiko
库来处理 SSH 连接,因此你需要安装它。pip install paramiko
-
配置 SSH 密钥:为了无密码登录到远程机器,你需要设置 SSH 密钥认证。
ssh-keygen # 生成 SSH 密钥对 ssh-copy-id user@remote-host # 复制公钥到远程主机
-
使用
--tx
参数:在运行 pytest 命令时,使用--tx
参数并指定ssh
作为传输方式。pytest --tx ssh=[user@remote-host] -n 2
这里
[user@remote-host]
是远程主机的 SSH 访问地址,-n 2
表示使用两个并发进程。
- 使用 Socket 进行远程通信
-
确定 master 和 worker 的地址和端口:你需要决定用于通信的 IP 地址和端口号。
-
启动 worker:在远程机器上,使用 pytest 的
--tx
参数和socket
传输方式启动 worker。pytest --tx socket=<master-ip>:<port> --n 2
这里
<master-ip>
是 master 节点的 IP 地址,<port>
是用于通信的端口号。 -
配置 master:在 master 节点上运行 pytest 时,同样使用
--tx
参数,但不需要指定远程地址。pytest --tx socket=<port> -n 2
假设你有一个 master 节点和一个 worker 节点,master 节点的 IP 是 192.168.1.100
,worker 节点的 IP 是 192.168.1.101
,你希望在这两个节点上并发执行测试。
-
在 worker 节点上(
192.168.1.101
),运行以下命令:pytest --tx socket=192.168.1.100:8888 --n 2
-
在 master 节点上(
192.168.1.100
),运行以下命令:pytest --tx socket=8888 -n 2
注意事项
- 确保网络配置允许 master 和 worker 节点之间的通信。
- 如果在云环境或使用了防火墙,确保相应的端口已开放。
- 使用 SSH 方式时,确保 SSH 密钥已正确设置,并且无密码登录是可行的。
七、参考
1、https://developer.moduyun.com/article/fb2ae78e-2e4f-4972-8f5d-b066ff163edb.html