pytest--xdist流程原理和执行顺序(--dist)

发布时间 2023-03-28 15:57:28作者: 测试-13

xdist原理和流程

xdist 的分布式类似于一主多从的结构,master 机负责下发命令,控制 slave 机;slave 机根据 master 机的命令执行特定测试任务。
在 xdist 中,主是 master,从是 workers。
分布式测试的原理:
(1)xdist 会产生一个或多个 workers,workers 都通过 master 来控制
(2)每个 worker 负责执行完整的测试用例集,然后按照 master 的要求运行测试,而 master 机不执行测试任务

分布式测试的流程:

1、创建 worker

(1)master 会在总测试会话(test session)开始前产生一个或多个 worker;

(2)master 和 worker 之间是通过 execnet 和网关来通信的;

(3)实际编译执行测试代码的 worker 可能是本地机器也可能是远程机器。

2、收集测试用例

(1)每个 worker 类似一个迷你型的 pytest 执行器;

(2)worker 会执行一个完整的 test collection 过程(收集所有测试用例的过程);

(3)然后把测试用例的 ids 返回给 master;

(4)master 是不会执行任何测试用例集的。

注:所以为什么脚本代码里有打印语句(print)通过分布式测试时结果没有输出用例的打印内容,因为主机并不执行测试用例,PyCharm 相当于一个 master。

3、master 检测 workers 收集到的测试用例集

(1)master 接收到所有 worker 收集的测试用例集之后,master 会进行一些完整性检查,以确保所有 worker 都收集到一样的测试用例集(包括顺序);

(2)如果检查通过,会将测试用例的 ids 列表转换成简单的索引列表,每个索引对应一个测试用例的在原来测试集中的位置;

(3)所有的节点都保存着相同的测试用例集,并且使用这种方式可以节省带宽,因为 master 只需要告知 workers 需要执行的测试用例对应的索引,而不用告知完整的测试用例信息。

4、测试用例分发

--dist-mode 选项

each:master 将完整的测试索引列表分发到每个 worker。

load:master 将大约25%的测试用例以轮询的方式分发到各个 worker,剩余的测试用例则会等待 workers 执行完测试用例以后再分发。

注:可以使用 pytest_xdist_make_scheduler 这个 hook 来实现自定义测试分发逻辑。

5、测试用例的执行

(1)workers 重写了 pytest_runtestloop(pytest 的默认实现是循环执行所有在 test session 这个对象里面收集到的测试用例);

(2)但是在 xdist 里, workers 实际上是等待 master 为其发送需要执行的测试用例;

(3)当 worker 收到测试任务, 就顺序执行 pytest_runtest_protocol;

(4)值得注意的一个细节是:workers 必须始终保持至少一个测试用例在任务队列里, 以兼容 pytest_runtest_protocol(item, nextitem)hook 的参数要求,为了将 nextitem 传给 hook;

(5)worker 会在执行最后一个测试项前等待 master 的更多指令;

(6)如果它收到了更多测试项, 那么就可以安全的执行 pytest_runtest_protocol,因为这时 nextitem 参数已经可以确定;

(7)如果它收到一个 "shutdown" 信号, 那么就将 nextitem 参数设为 None, 然后执行 pytest_runtest_protocol。

6、测试用例再分发

--dist-mode=load

(1)当 workers 开始/结束执行时,会把测试结果返回给 master,这样其他 pytest hook 比如(pytest_runtest_protocol 和 pytest_runtest_protocol 就可以正常执行);

(2)master 在 worker 执行完一个测试后,基于测试执行时长以及每个 work 剩余测试用例综合决定是否向这个 worker 发送更多的测试用例。

7、测试结束

(1)当 master 没有更多执行测试任务时,它会发送一个 "shutdown" 信号给所有 worker;

(2)当 worker 将剩余测试用例执行完后退出进程;

(3)master 等待所有 worker 全部退出;

(4)此时仍需要处理诸如 pytest_runtest_logreport 等事件。

以上原文链接:https://blog.csdn.net/wangmcn/article/details/121080902

 

获取测试用例打印

使用xdist分布式运行测试用例时,可以发现测试用例中的print并没有在pycharm的终端打印出来。这个原因在上面流程的第二个点收集用例,就已经告诉了原因。

原因:pycharm相当于是一个master,只接收worker执行完成后返回的测试用例的ids(对应每个测试用例的位置),并不包含完整的测试结果信息

那么想查看分布式运行中测试用例的print打印的话,可以与pytest-html插件搭配使用(这个后续再详细介绍),pytest-html插件也可以通过pip进行安装

pip install pytest-html 

在执行用例的时候,加上参数--html html报告路径即可

test_xdist.py示例代码: 

import time


def test_01():
    time.sleep(2)
    print('用例一执行')

def test_02():
    time.sleep(2)
    print('用例二执行')

def test_03():
    time.sleep(2)
    assert 2 == 2
    print('用例三执行')

def test_04():
    time.sleep(2)
    print('用例四执行')

  与pytest-html搭配使用,终端命令pytest test_xdist.py -n 4 -sv --html=report.html输出结果:

 可以看到pycharm终端并没有打印出测试用例中的输出,只显示了每条测试用例的位置以及测试情况。 但html报告文件却记录了每个worker所有的测试情况,report.html输出定义在当前路径下,可以直接在浏览器中打开

查看Result部分,将每条用例折叠框点击打开,即可看到每条测试用例的打印输出

 

 

 xdist执行顺序

pytest-xdist默认是无序执行,可以通过--dist来控制执行顺序

--dist=loadscope:按照同一个模块module下的函数和同一个测试类class下的进行分组,将每个测试组发给可以执行的cpu核数(worker),确保同一个组的测试用例在同一个进程中执行。class分组优先于按模块module分组
--dist=loadfile:按照同一个文件名来分组,然后将每个测试组发给可执行的cpu核数(worker),确保同一个组的测试用例在同一个进程执行

可以按照以下测试文件夹目录来验证:

  test_case.py:

import pytest

#参数化,传入参数
@pytest.mark.parametrize('n',list(range(3)))
def test_get_info(n):
    print('===获取用户个人信息===',n)

 test_baidu文件夹下test_case1.py:

import pytest
from time import sleep

@pytest.mark.parametrize('n',list(range(2)))
def test_case1_1(n):
    sleep(1)
    print('===baidu 执行测试用例test_case1_1====',n)

@pytest.mark.parametrize('n',list(range(2)))
def test_case1_2(n):
    sleep(1)
    print('===baidu 执行测试用例test_case1_2====',n)

 test_baidu文件夹下test_case2.py:

import os
def test_01():
    print('====1===========')
class Test_01:
    def test_02(self):
        print('=======2=============')

 构建完成后,那么来看下执行顺序--dist=loadscope和--dist=loadfile之间的区别

--dist=loadscope

pycharm终端执行pytest -n auto --dist=loadscope -sv输出结果:

  只看有gw进程编号的打印即可。

test_case.py属于一个module模块,且里面没有测试类,不需要再次进行分组。可以看到3组测试用例都被分配到gw0这个进程中进行执行

 test_case1.py属于一个module模块,没有定义测试类,所有测试用例是一个分组,在同一个woker(gw1)中执行:

test_case2.py属于一个module模块,但其中定义了一个Test_01测试类,根据class分组优先于module分组,test_case2.py:Test_01测试类下的所有测试用例会再分一个组,也就是在同一个woker(gw3)中执行。如下图:

 test_case2.py测试类外的test_01用例则单独分一个组,在woker(gw2)中执行

--dist=loadfile

pycharm终端执行pytest -n auto --dist=loadfile -sv输出结果:

 这个更直观了,按照同一个文件名来分组,也就3个文件名:test_case.py、test_case1.py、test_case2.py,依次分配给gw0,gw1,gw3中执行用例,不需要区分测试类啥的,一个文件下的测试用例就是在一个woker中执行的