Python 多线程运行 pytest 用例,解决 pytest-xdist session 级别的 fixture 运行次数不合理和进程切换更消耗 cpu 的问题

test · 2021年08月19日 · 最后由 大瓶子 回复于 2024年03月27日 · 8586 次阅读

背景

在用 pytes 做自动化测试的时候,有时候我们的用例会非常多,特别是在用例参数化的时候。很容易就会有上千条的用例。可能每个用例又会有 IO 等待。如果顺序运行的话,就会很耗时。所以希望可以并发运行用例。这时可以用 pytest-xdist。这个网上有很多教程,这里不说了。主要说一下 pytest-xdist 的缺点

pytest-xdist 的缺点

  • pytest-xdist 是多进程运行的,进程切换很耗资源。并发不能调太大。否则会把 cpu 消耗光。如果用 pytest-xdist 同时运行几千个有 IO 等待的用例, 会立刻把 cpu 耗尽。

  • pytest-xdist 的运行机制破坏了 pytest 对 fixture 原始定义。按照原始的定义,session 级别的 fixture,在一次自动化测试中只会运行一次。但是 pytest-xdist 是每个进程都会运行一次。相当与一些全局性的前置条件会执行多次,在有些情况下可能会有问题的。比如你想在运行所有测试用例之前,先在 session 级别的 fixture 重启测试的程序,pytest-xdist 的每个进程都会执行一次,相当于会重启多次。有可能会出现某些进程已经在运行测试用例了,但另一些进程还在 session 级别的 fixture 里重启服务。影响了测试用例的运行。

为了解决上述问题。我为 pytest 写了这个插件。我把它叫做 pytest-multithreading。有以下好处

  • 1.它是基于多线程实现的。线程间的切换消耗的 cpu 更少。所以可以更高并发的运行用例。
  • 2.session 级别的 fixture 也只会运行一次。不会像 pytest-xdist 运行多次
  • 3.增加了一个哪些用例不并发运行的功能

pytest-multithreading 的基本使用方法

先安装 pytest-multithreading。直接输入 pip install pytest-multithreading 安装

运行时用--th 参数指定最大创建的线程数,就可以并发运行测试用例了。
以下是使用示例

import time

import pytest


test_data_list = [i for i in range(1,1000)]
@pytest.mark.parametrize("i",test_data_list)
def test_multithreading(i):
    time.sleep(2)
    assert 1 == 1


if __name__ ==  "__main__":
   #--th是指定最大线程数,即最大并发运行的用例。这1000个用例会并发运行
    pytest.main(["test_pytest.py","-x","--th=1000"])


pytest-multithreading 插件可以标记特定的测试用例不并发运行

我们知道如果想要并发运行测试用例。就要让所有的用例都没有依赖关系,每个用例不能干扰其它用例的运行。但是有些用例要 reload 程序,这会干扰其它用例的运行。所以要让这些用例独立运行,不能让其参与并发运行。所以 pytest-multithreading 做了这个支持标记那些用例不并发运行的功能

使用示例如下:

import time

import pytest


test_data_list = [i for i in range(1,100)]
@pytest.mark.parametrize("i",test_data_list)
def test_multithreading(i):
    print("test_concurrent")

#被@pytest.mark.notconcurrent标记的用例不会参与并发运行,并发运行的用例会等这些被标记的用例运行结束后才开始并发运行
@pytest.mark.notconcurrent
def test_notconcurrent():
    print("test_notconcurrent")
    time.sleep(10)



if __name__ ==  "__main__":
    pytest.main(["test_pytest.py","-x","--th=100","-s"])

pytest-multithreading 与 pytest-parallel 的对比

一开始写 pytest-multithreading 的时候,是为了可以更高并发的运行 pytest 的用例。那时还不知道其实已经有 pytest-parallel 这个支持多线程运行用例的插件了。它们两个的对比如下:

1.pytest 的-x 参数失效了。pytest 原有的功能如果输入-x 参数时,如果有一个用例失败时,pytest 会立刻停止运行。但是启用了这两个插件后,pytes 会运行完所有用例。

2.启用这两个插件后,allure-pytest 生成的测试报告将无效

3.pytest-multithreading 比 pytest-parallel 有一个优点。pytest-paralle 运行 session 级别的 fixture。每个线程都会运行一次。这是和 pytest-xdist 同样的问题。而且如果 session 级别的 fixture 是有 IO 等待的话。pytest-paralle 执行用例的总时长将会大大增加

总结

虽然 pytest-multithreading 有以上缺点,但是我还是觉得它对我的工作是很有帮助的。因为我有几千个有 IO 等待的用例需要经常运行。这个时候 pytest-multithreading 能够最快的帮我运行完。总结起来说就是 pytest-multithreading 的适用场景是有很多有 IO 等待的用例需要快速运行。以后再找时间兼容下 allure-pytest 插件和-x 参数的问题

共收到 34 条回复 时间 点赞

牛啊 mark 一下

mark 一下

mark 一下

mark 一下

test 关闭了讨论 08月20日 08:59
test 重新开启了讨论 08月20日 08:59

这个帖子提出了使用多线程执行自动化测试的问题:https://testerhome.com/topics/30536

我把需要 IO 操作的流程单独一条线程出来,3W 用例原本 40 分钟,变成了 11 分钟,所以我感觉也可这样优化一下。

test #9 · 2021年08月23日 Author
MarvinWu 回复

你好,文件锁的方式,我之前有了解过,但是你这个例子是不是写错了呢?你这样加锁,只是 with 里的代码不允许并行运行,实际每个进程都会进去 with 里的代码,还是执行多次了。文件锁的方式应该是在执行完一次的时候把数据写到一个文件,其它进程发现有这个文件的时候,就不执行了。才能实现仅执行一次。我这个方式就不用在每个 fixture 做这个事情了,我在框架代码里加锁了

test #10 · 2021年08月23日 Author
锅锅的锅 回复

你好,没懂你的意思,IO 操作的流程单独一条线程出来,单线程运行不是还是会阻塞吗?

test #12 · 2021年08月23日 Author
Thirty-Thirty 回复

你好,你说的是 cpython 的多线程实际并没法用到 cpu 的多核性能吗?我这个是用在大量 IO 等待的场景,不是用在 CPU 密集型的,所以暂时单进程就够用了。后续在改成支持多进程 + 多线程

test 回复

生产者消费者模式

test 回复

软件自动化测试很少考虑硬件层 (CPU 多核),场景也不会以硬件开销 (CPU 密集型) 进行划分。不建议个人尝试实现多线程执行自动化测试,坑较多

使用了pytest-multithreading 发现个问题,pytest-html上的记录日志,会被打乱,我这样调用:

pytest -s testcase/  --th 10 --html=report.html --self-contained-html --capture=sys


hi,一运行报这个错,python3.9,能帮看看吗

嗨,多线程运行用例,用例就是可能交叉执行的,你想用例不允许交叉执行,就不能用多线程了


大佬 一直报这个错 辛苦帮忙看看

请问我这里安装不上这个库,已经试过豆瓣源和清华源了, 都不行...

test #19 · 2022年10月11日 Author
古一 回复

看起来是你的 python 环境没有 os.environ.putenv() 这个函数,你直接运行这个 os.environ.putenv("test","test") 应该会报错。你是什么系统?什么 python 版本的?我去测试下。

test #20 · 2022年10月11日 Author
yuewei 回复

看起来是你的 python 环境没有 os.environ.putenv() 这个函数,你直接运行这个 os.environ.putenv("test","test") 应该会报错。你是什么系统?什么 python 版本的?我去测试下。

test #21 · 2022年10月11日 Author

你是在什么系统下安装的呢?我测试过了在 windox 和 linux 都可以安装成功

test 回复

我是 3.9.5 版本,报错

INTERNALERROR>   File "C:\Users\hli\Documents\PycharmProjects\UIAuto\venv\lib\site-packages\pytest_th\__init__.py", line 143, in pytest_sessionstart
INTERNALERROR>     os.environ = ThreadLocalEnviron(os.environ)
INTERNALERROR>   File "C:\Users\hli\Documents\PycharmProjects\UIAuto\venv\lib\site-packages\pytest_th\__init__.py", line 90, in __init__
INTERNALERROR>     env.putenv,
INTERNALERROR> AttributeError: '_Environ' object has no attribute 'putenv'

降级到 3.8.10 运行成功了

notconcurrent 用例怎么改成并发后执行呢?

test #25 · 2022年11月21日 Author
sadboitom 回复

不要在用例加上@pytest.mark.notconcurrent 装饰器,它就会参与并发执行呀

test 回复

我的意思是 想让某条用例在并行用例执行结束之后执行

test #26 · 2022年11月21日 Author
sadboitom 回复

现在 notconcurrent 的用例统一是在并发用例之前执行,暂不支持让它在并发后执行,这样有什么问题吗?要改下才能支持让 notconcurrent 的用例在并发结束后再允许

我执行了 main 方法, 却没有多线程跑.

你能留一个 demo 工程代码在这里吗, 谢谢啦

test #28 · 2022年12月20日 Author
sun 回复

上面的代码例子就是 demo 代码,你这个运行方式有问题,这样运行,获取不到输入参数

Hi,大佬。我想要多线程的粒度保持在类级别或包级别,而不是在函数级别,即每个线程处理一个测试包,要怎么设置呢?

test #30 · 2023年03月29日 Author
hear_rain 回复

一个简单的方法,你在主线程,启动多个子线程去执行 pytest.main(["你的测试包路径"]),
这种方法,allure 也是能汇总测试报告的

大佬,你好。multithreading-1.0.4 。运行后显示 no tests ran,这是怎么回事啊?

test #31 · 2023年05月06日 Author
interface007 回复

我这边没遇到过这个问题,有截图吗?没安装这个插件之前你的用例运行正常吗?是安装完这个插件就有问题了吗?

test 回复

不使用此插件 --th=... case 能正确运行

还在跟新吗,目前的插件好像用不了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册