当前位置: 首页>后端>正文

pytest-xdist分布式执行用例

pytest-xdist可以让自动化测试用例分布式执行,从而节省自动化测试时间, pytest-xdist是属于进程级别的并发

一. 分布式执行用例的设计原则

  • 用例之间相互独立,没有依赖关系,用例可以完全独立运行
  • 用例执行没有顺序,随机顺序都能正常执行
  • 用例可以重复执行,运行结果不会影响其他用例
    二. 安装
    pip3 install pytest-xdist
    三.应用
    原理:xdist会产生一个或多个workers,workers都通过master来控制
    每个worker负责执行完整的测试用例集,然后按照master的要求运行测试,而master机不执行测试任务
    测试代码
# 开发时间:2023/11/6 17:45
import time

from selenium import webdriver
from selenium.webdriver.common.by import By

url = 'https://www.baidu.com'


class TestXdistDemo:
    def setup_method(self):
        self.driver = webdriver.Chrome()

    def teardown_method(self):
        self.driver.quit()

    def test_case_01(self):
        self.driver.get(url)
        time.sleep(1)
        self.driver.find_element(By.NAME, 'wd').send_keys('Python')
        self.driver.find_element(By.ID, 'su').click()
        time.sleep(3)

    def test_case_02(self):
        self.driver.get(url)
        self.driver.find_element(By.NAME, 'wd').send_keys('Java')
        self.driver.find_element(By.ID, 'su').click()
        time.sleep(3)

    def test_case_03(self):
        self.driver.get(url)
        self.driver.find_element(By.NAME, 'wd').send_keys('go')
        self.driver.find_element(By.ID, 'su').click()
        time.sleep(3)

    def test_case_04(self):
        self.driver.get(url)
        self.driver.find_element(By.NAME, 'wd').send_keys('php')
        self.driver.find_element(By.ID, 'su').click()
        time.sleep(3)

    def test_case_05(self):
        self.driver.get(url)
        self.driver.find_element(By.NAME, 'wd').send_keys('c++')
        self.driver.find_element(By.ID, 'su').click()
        time.sleep(3)

不使用分布式的执行pytest

collected 5 items                                                                                                                     

xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_01 PASSED
xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_02 PASSED
xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_03 PASSED
xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_04 PASSED
xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_05 PASSED

========================================================= 5 passed in 33.32s 

使用分布式执行 pytest -n 3
pytest -n num (num代表使用num个CPU)
pytest -n auto(auto可以自动检测到系统的CPU核数,使用auto等于利用了所有的CPU来跑用例,CPU占用率会特别高)

3 workers [5 items]     
scheduling tests via LoadScheduling

xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_03 
xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_02 
xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_01 
[gw1] PASSED xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_02 
xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_05 
[gw2] PASSED xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_03 
[gw0] PASSED xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_01 
xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_04 
[gw1] PASSED xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_05 
[gw0] PASSED xdisttestcases/test_xdist_demo.py::TestXdistDemo::test_case_04 
========================================================= 5 passed in 15.64s 

可见,使用三个进程执行,同样的用例执行时间从33.32s缩短到16.64s
自定义执行模式
将按照同一个作用域来分组,然后将每个测试组发给可以执行的worker,确保同一个组的测试用例在同一个进程中执行
--dist=loadscope
将按照同一个文件名来分组,然后将每个测试组发给可以执行的worker,确保同一个组的测试用例在同一个进程中执行
--dist=loadfile
将每个用例,分贝发给所有的执行器worker,相当于开了几个执行器worker
--dist=each

问题:如何让scope=session的fixture在test session中仅仅执行一次
pytest-xdist是让每个worker执行属于自己的测试用例集下的所有测试用例,这意味着在不同进程中,不同的测试用例可能会调用同一个scope范围级别较高(例如session)的fixture,该fixture就会被执行多次,这不符scope=session 的预期
解决:可以通过锁定文件进行进程通信来实现

# 开发时间:2023/11/6 18:25
import json
import os
import uuid
from os import environ

import FileLock as FileLock
import pytest


class TestXdist:
    @pytest.fixture(scope='session', autouse=True)
    def login(self, tmp_path_factory, worker_id):
        if worker_id == 'master':
            """
            自定义代码块
            比如:登陆请求,新增数据,清空数据库等等
            """
            uuid_value = uuid.uuid1()
            token = uuid_value.hex
            print('fixture:请求登陆接口,获取token', token)
            os.environ['token'] = token
            return token
        # 如果是分布式运行
        # 获取所有字节点共享的临时目录,无需修改
        root_tmp_dir = tmp_path_factory.getbasetemp().parent
        fn = root_tmp_dir / "data.json"
        with FileLock(str(fn) + ".lock"):
            if fn.is_file():
                # 缓存文件中读取数据,像登陆操作的话就是token
                token = json.loads(fn.read_text())
                print(f"读取缓存文件,token是{token}")
            else:
                """
                自定义代码块
                """
                uuid_value = uuid.uuid1()
                token = uuid_value.hex
                print('fixture:请求登陆接口,获取token', token)
                fn.write_text(json.dumps(token))
                print(f"首次执行,token是{token}")
                # 最好将后续需要保留的数据存在某个地方,比如这里是os的环境变了
            os.environ['token'] = token
        return token

示例只需要执行一次login
当第一次请求这个fixture时,则会利用FileLock仅产生一次fixture数据
当其他进程再次请求这个fixture时,则会从文件中读取数据


https://www.xamrdz.com/backend/3vv1945769.html

相关文章: