【附源码】pytest多并发实践

pytest系列——pytest-xdist插件之多进程运行测试用例|| pytest-parallel插件之多线程运行测试用例 也方便你下次能够快速查找。如有不懂还要咨询下方小卡片,博主也希望和志同道合的测试人员一起学习进步在适当的年龄,选择适当的岗位,尽量去发挥好自己的优势。我的自动化测试开发之路,一路走来都离不每个阶段的计划,因为自己喜欢规划和总结,测试开发视频教程、学习笔记领取传送门!! 阅读详情

目录层级结构图

在这里插入图片描述

主执行文件(runner.py)

from concurrent.futures.process import ProcessPoolExecutor
import os
import time
import pytest
import yaml
from meet.public import *
from meet.concurrent_startup import *

devices_list = ['Your device uid', 'Your device uid']


def runnerPool():
    with open('kyb_caps.yaml', 'r', encoding="utf-8")as file:
        data = yaml.load(file, Loader=yaml.FullLoader)
    print(data)
    devices_Pool = data
    with ProcessPoolExecutor(len(devices_Pool)) as pool:
        pool.map(runPytest, devices_Pool)


def runPytest(device):
    print(f"cmdopt is {device}")
    report = f"report-{device['deviceName']}".split(":", 1)[0]
    report_dir = mkdir_dir(report)
    now_time = time.strftime("%H-%M-%S", time.localtime())
    print(f"pool run device is {device['deviceName']}")
    pytest.main(["-s", "./TestCases/", f"--cmdopt={device}", "--alluredir", f"./{report_dir}/{now_time}/xml"])
    time.sleep(1)
    os.system(f"allure generate ./{report_dir}/{now_time}/xml -o ./{report_dir}/{now_time}/html")


if __name__ == '__main__':
    """
    获取服务的端口
    并发启动appium的服务
    多设备启动执行用例
    release端口
    """
    ports = []
    appium_start_sync()
    runnerPool()
    for i in range(len(devices_list)):
        port_num = 4723 + 2 * i
        ports.append(port_num)
    print(ports)
    for t in ports:
        release_port(t)

公有函数(public.py)

import time


def mkdir(path):
    """创建目录函数"""
    import os
    path = path.strip()   # 去除首位空格
    path = path.rstrip("\\")  # 去除尾部 \ 符号
    is_exists = os.path.exists(path)  # 判断路径是否存在
    if not is_exists:
        os.makedirs(path)  # 不存在目录则创建
        print(path + "创建成功")
        return True
    else:
        print(path + "目录已存在")  # 如果目录存在则不创建,并提示目录已存在
        return False


def mkdir_dir(path):
    """创建当天相应的目录"""
    now_date = time.strftime("%Y-%m-%d", time.localtime())
    main_path = f"{path}"
    global dir_path
    dir_path = (main_path + '/' + now_date)
    mkdir(dir_path)
    return dir_path


def get_failed_screenshot(self, current_case_name):
    """获取失败截图函数"""
    now_date = time.strftime("%Y-%m-%d", time.localtime())
    now_time = time.strftime("%Y-%m-%d %H-%M-%S", time.localtime())
    global dir_path
    self.driver.get_screenshot_as_file(dir_path + "/" + now_time + " " + current_case_name + ".png")

pytest的配置文件(conftest.py)

import pytest
from appium import webdriver


def pytest_addoption(parser):
    parser.addoption("--cmdopt", action="store", default="device", help="None")


@pytest.fixture(scope="session")
def cmdopt(request):
    return request.config.getoption("--cmdopt")


@pytest.fixture
def connectDevice(cmdopt):
    device = eval(cmdopt)
    device_caps = {}
    device_caps["platformVersion"] = device["platformVersion"]
    device_caps["platformName"] = device['platformName']
    device_caps["deviceName"] = device['deviceName']
    device_caps["udid"] = device['udid']
    device_caps["appPackage"] = device['appPackage']
    device_caps["appActivity"] = device['appActivity']
    device_caps["newCommandTimeout"] = device["newCommandTimeout"]
    device_caps["noReset"] = True
    device_caps["noSign"] = True
    device_caps["unicodeKeyboard"] = True
    device_caps["resetKeyboard"] = True
    device_caps["systemPort"] = device["systemPort"]
    remote = "http://127.0.0.1:" + str(device["port"]) + "/wd/hub"
    driver = webdriver.Remote(remote, device_caps)
    yield driver
    driver.close_app()
    driver.quit()

Appium并发开启端口文件(concurrent_startup.py)

# coding=utf-8

import subprocess
import socket
from time import ctime
import multiprocessing
import os
from meet.public import *


def check_port(host, port):
    """
    检测指定的端口是否被占用
    :param host:
    :param port:
    :return:
    """

    # 创建socket对象
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect((host, port))
        s.shutdown(2)
    except OSError as msg:
        print('----------port %s is available! ----------' % port)
        print(msg)
        return True
    else:
        print('----------port %s already be in use !-----------' % port)
        return False


def release_port(port):
    """
    释放指定的端口
    :param port:
    :return:
    """

    # 查找对应端口的pid
    cmd_find = 'netstat -aon | findstr %s' % port
    print(cmd_find)

    # 返回命令执行后的结果
    result = os.popen(cmd_find).read()
    print(result)

    if str(port) and 'LISTENING' in result:
        # 获取端口对应的pid进程
        i = result.index('LISTENING')
        start = i + len('LISTENING') + 7
        end = result.index('\n')
        pid = result[start:end]

        # 关闭被占用端口的pid
        cmd_kill = 'taskkill -f -pid %s' % pid
        print(cmd_kill)
        os.popen(cmd_kill)

    else:
        print('----------port %s is available !-----------' % port)


def appium_start(host, port):
    """
    启动appium服务
    :param host:
    :param port:
    :return:
    """

    bootstrap_port = str(port + 1)

    # cmd = 'start /b appium -a ' + host + ' -p ' + str(port) + ' --bootstrap-port ' + str(bootstrap_port)  # linux下
    cmd = 'start node G:\\Appium\\resources\\app\\node_modules\\appium\\build\lib\\main.js -a ' + host + ' -p ' + str(
        port) + ' --bootstrap-port ' + str(bootstrap_port)  # win调试

    print('%s at %s' % (cmd, ctime()))
    mkdir("appium_log")
    subprocess.Popen(cmd, shell=True, stdout=open('./appium_log/' + str(port) + '.log', 'a'), stderr=subprocess.STDOUT)


def start_appium_action(host, port):
    """
    检测端口是否被占用,如果没有被占用则启动appium服务
    :param host:
    :param port:
    :return:
    """
    if check_port(host, port):
        appium_start(host, port)
        return True
    else:
        print("----------appium %s already start!----------" % port)
        return True


def appium_start_sync(devices_list):
    """
    并发启动appium服务
    :return:
    """
    appium_process = []
    # 加载appium进程
    for i in range(len(devices_list)):
        host = '127.0.0.1'
        port = 4723 + 2 * i
        appium = multiprocessing.Process(target=start_appium_action, args=(host, port))
        appium_process.append(appium)

    # 启动appium服务
    for appium in appium_process:
        appium.start()
    for appium in appium_process:
        appium.join()

用例层(test_demo.py)

import allure
from selenium.webdriver.support.wait import WebDriverWait
from unittest import TestCase
import pytest
import time
from appium import webdriver


class TestMyConcurrent(object):

    @allure.feature("group_Call")
    @allure.story("search_Case")
    def test_001(self, connectDevice):
        """
        测试用例一
        connectDevice = driver
        :param connectDevice:
        :return:
        """
        time.sleep(5)
        connectDevice.find_element_by_id("yiyayiyayo").click()
        time.sleep(2)
        print("已点击咿呀咿呀哟")
        time.sleep(5)

    @allure.feature("group_Call")
    @allure.story("search_Case")
    def test_002(self, connectDevice):
        """
		测试用例二
        :return:
        """
        time.sleep(5)
        connectDevice.find_element_by_id("sousuokuang").click()
        time.sleep(2)
        connectDevice.find_element_by_id("sousuokuang").send_keys("天气")
        time.sleep(2)
        connectDevice.find_element_by_id("sousuo_btn").click()  # 点击搜索
        print("已完成点击搜索")
        time.sleep(5)
        connectDevice.keyevent(4)
        time.sleep(2)
        connectDevice.keyevent(4)
        print("已返回主页")

    @allure.feature("group_Call")
    @allure.story("search_Case")
    def test003(self, connectDevice):
        """测试用例三"""
        while True:
            try:
                connectDevice.find_element_by_xpath("//*[contains(@text,'更多')]").click()
                break
            except:
                time.sleep(5)
        print("点击完成更多")
        time.sleep(5)
        connectDevice.keyevent(4)
        print("从更多页面返回主页")
        time.sleep(4)

设备信息配置文件(kyb_caps.yaml)

- platformName: Android
  platformVersion: "10"
  deviceName: 123456gf
  appPackage: package_name
  appActivity: package_home_name
  udid: 123456gf
  systemPort: 8208
  newCommandTimeout: 6000
  port: 4723



- platformName: Android
  platformVersion: "11"
  deviceName: sdakla9312
  appPackage: package_name
  appActivity: package_home_name
  udid: sdakla9312
  systemPort: 8209
  newCommandTimeout: 6000
  port: 4725

   
   
   

运行效果截图

在这里插入图片描述

Pytest-xdist并行执行用例时的“坑” 所以,采用了pytest-xdist,不曾想,也是自己挖了“坑”,自己填。其次,换了settings.json后,初始化产生数据以及数据的写入都没有问题,而新的问题又出现。后来经过调试发现,第一个进程运行时,将产生的初始化数据保存进文件,正是因为要落盘(写入文件),肯定不如数据在内存中运行快,此时,第二个进程已经运行到读取文件。例如,登录、初始化。最后,因为修改了settings.json文件,原有工程中,采用settings.xxx的方式引用变量,就必须全部修改为settings['xxx']。..... 阅读详情

相关推荐

pytest框架源码解析一

方法内部创建了一个pluginmanager的对象,然后以此为入参创建了一个config的对象,并且把args,plugins和dir(当前文件路径-即运行主方法的文件路径)都存到config对象中去。方法中先是判断了args是不是None(若是none,则再取一下命令行中的参数),是不是路径参数(若是路径类型则转成字符串),是不是 不是list(如果不是则抛出异常)。_.py下,入参有两个,args和plugins,这边我们都是没有传的(但是有默认值),返回值为int类型的退出码,一般成功了退出码为0。

evelol7的博客 1130

pytest多进程/多线程执行测试用例

利用多进程/多线程并行执行测试用例,提升测试执行效率。

weixin_45005677的博客 6608

深入解析 pytest 数据驱动原理:从源码到实践

在自动化测试中,数据驱动测试(Data-Driven Testing)是提高测试覆盖率和维护效率的核心技术。pytest 作为 Python 生态中最流行的测试框架之一,其 @pytest.mark.parametrize 装饰器为数据驱动提供了优雅的实现。本文将深入 pytest 数据驱动的源码实现,揭示其核心设计思想,并通过动手实践案例,帮助开发者掌握这一技术的底层原理,最终实现自定义数据驱动框架的能力。

ltm1410010510的博客 837

Pytest多进程或多线程执行测试实例

实际项目中的用例数量会非常多,几百上千;如果采用单进程串行执行的话会非常耗费时间。假设每条用例耗时2s,1000条就需要2000s $\approx$ 33min;还要加上用例加载、测试前/后置套件等耗时;导致测试执行效率会相对低。

weixin_47648853的博客 2624

深入解析Pytest 底层源码

Pytest 的底层架构是一个基于 pluggy钩子机制的微内核架构。其核心流程是一个固定的链条,链条上的每个环节都通过钩子向外暴露,允许插件介入。这种设计使得 Pytest 极其灵活和强大。启动: 解析配置,加载所有插件(包括核心插件)。收集: 遍历文件系统,发现测试用例,并生成Item对象。执行: 对每个Item,依次执行setup->call->teardown协议。报告: 在整个过程中,通过不同的钩子收集信息,最终生成报告。

monk all the way 621

Pytest学习笔记7——接口自动化测试框架实战附源码(开荒篇)

  引言   前面介绍了pytest框架一些基础知识与基本用法,掌握了一定基础后,我们可以进行项目实战,搭建pytest接口自动测试框架。   框架设计思路   画了一张草图,大家自行领会。   项目结构   源码展示   将test_weatherapi.py测试用例源码附上,关键部分都有备注,知识点覆盖之前所讲的。 city = ['北京','上海','广州','...

像蚂蚁一样工作,像蝴蝶一样生活 8754

接口测试多线程执行用例-Pytest-xdist

在目前实际项目下的接口自动化用例会非常多,如果采用单进程串行执行的话会非常耗费时间,在实际项目中如服务端开发对底层的公用模块进行改动,此时我们测试QA就需要回归所有服务的接口,这时执行所有服务下接口自动化用例可能少则需要半小时、多则需要好几个小时,这是很低效率的。为了节省项目回归测试的时间,需要多个测试用例同时并行执行,这就是一种分布式场景来缩短测试用例的执行时间,提高效率。但是在分布式执行用例要遵循以下的原则:1.用例之间是相互独立的,没有依赖关系,可以独立运行;

oqqChin12的博客 3032

人工智能(AI)简史:推动新时代的科技力量

人工智能(AI,Artificial Intelligence)是计算机科学的一个分支,旨在研究和开发可以模拟、扩展或增强人类智能的系统。它涉及多种技术和方法,包括机器学习、深度学习、自然语言处理(NLP)、计算机视觉、专家系统等。

qq_17153885的博客 1万+

【pytest高阶】-1- 源码的走读方法及插件hook

Dog# 用 @hookimpl 标记:这是插件对 say_after 钩子的实现!@hookimplprint(f'我是插件1,我收到了软件的参数 {arg1}, {arg2}')@hookimplprint(f'我是插件2,我收到了软件的参数 {arg1}, {arg2}')插件的作用:每个插件里的say_after函数,必须和钩子函数(Dog里的say_after)参数一致(都有arg1, arg2),这样插件管理器才能识别~

2401_83117850的博客 753

基于STM32的多普勒雷达测速

基于多普勒雷达传感器,以STM32单片机为主控芯片,根据不同模块检测距离的不同,使用不同多普勒雷达传感器实现对远近距离车辆行驶速度及方向的测量。 1、基础知识 无线电探测和测距,是用无线电(电磁波)方法发现目标并测定其空间位置的装置。随着雷达技术的发展,现代雷达不仅可以对目标进行定位,还可以测量目标的速度、对目标进行成像甚至测量目标的大小和材料特性。所有利用雷达波来检测物体速度的原理,其理论基础皆源自于“多普勒效应”,...

weixin_52801934的博客 6316

Unity——Scroll View 滑动居中、居中的放大、其他的缩小(支持横竖Scroll View)

一、效果 二、上代码,两种方式。 (第一种是在Update中用插值完成的,如果不想再项目中看到Update,下面的另一种改了一段用DoTween实现的,需要导入一下DoTween) using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using Unit...

SiReSo 7662

pytest源码_pytest源码目录概要分析

帮忙多点点文章末右下角的“好看”支持下,也可以将本文分享到朋友圈或你身边的朋友,谢谢Pytest是什么?pytest是一个非常成熟的全功能的Python测试框架。怎么安装?pip install pytestgithub项目地址在哪?https://github.com/pytest-dev/pytestpytest源码结构在python安装目录的lib/site-packges下,分为pyte...

weixin_39722917的博客 824

OpenGL计算着色器工作组设置测试

先看看不同的显卡上工作组的数量情况。 glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, data); glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, data + 1); glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2, data...

qq_26328385的博客 1485

pytest-assume 源码

【代码】pytest-assume 源码。

Wangzgdz的博客 871

COMSOL电磁仿真-网格剖分-边界层

介绍COMSOL在低频电磁场中边界层网格的剖分方法。

奇妙水果的技术博客,欢迎交流技术~ 4493

WPF窗口定位技巧:从基础到高级的三种实现方法

本文详细解析WPF窗口定位的三种核心方法:屏幕居中(CenterScreen)、父窗口居中(CenterOwner)和手动定位(Manual)。通过对比其适用场景、实现代码与注意事项,帮助开发者根据实际需求选择最佳方案,从而提升WPF应用程序的用户体验与专业度。

weixin_29224309的博客 273

Pytest源码解析: 解析Pytest 插件系统

本文将带你探索 Pytest 最核心的插件,并以 Pytest-xdist 为例,深入剖析其底层实现原理,揭示 Pytest 插件系统的设计之美。Pytest-xdist 会在这些钩子中识别自己是 Worker 的身份,并相应地调整行为,例如关闭在主进程中已经完成的不必要操作,或者设置与 Master 通信所需的组件。通过这种基于钩子的架构,Pytest 变得极其灵活和可扩展,Pytest-xdist 正是利用这一点,将一个单进程测试运行器成功地转变为一个强大的分布式测试平台。

Hacker_xingchen的博客 1076

目标检测YOLO实战应用案例100讲-跨模态、多级别融合的RGB-D显著目标检测方法研究(续)

在第二章介绍了本研究的跨模态特征融合方法,第二章对于多级别特征的融合, 使用的是与基线模型baseline相同的方法:相邻级别的特征中,高级特征进行上采 样到与低级特征相同的尺度,再将高级特征通过降维得到与低级特征相同的通道数 量,最后对二者进行特征叠加,如此自顶向下的进行多级别的特征融合。本章主要 介绍本研究在特征层面的另一发现,并基于此发现引出的本文的第二个创新点:多 级别特征的融合方法。另外,在网络最深层,本文设计了一个新的模块来获取大的 感受野,对于大尺度目标能够增加更多语义信息,提升检测效果。 骨

qq_36130719的博客 469

【pytest框架源码分析五】pytest插件的注册流程

对比可以看到这些文件中都有实现,其中mark,setuonly,setupplan中的实现方法都加了@pytest.hookimpl(tryfirst=True),按照之前介绍的原则,后加的先执行,加了tryfirst的先执行,这里的执行顺序为setupplan,setuponly,mark,cacheprovider,…这里可以看到,其先取了下对应的接口方法有无opts参数,如果没有,则判断一下方法是否是以pytest_开头的,如果是,则添加opts参数。这个main方法,最重要的有两步,第一步是。

evelol7的博客 1121

【WRF-Urban数据集】 WRF 模型城市冠层参数 GloUCP 的使用

【数据集】 WRF 模型城市冠层参数 GloUCP 的使用

WW、forever的博客 484

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

pytest源码_Pytest下的常用插件

目前Pytest是最受欢迎的测试框架,今天就给大家推荐几款实用又好上手的插件,赶快往下看,肯定有你的菜!!!01Pytest-ordering作用:控制测试用例的执行顺序使用: 1.安装:pip install pytest-ordering 2.在测试用例上通过装饰器的形式标注:通过指定order,如@pytest.mark.run(order);或...

weixin_42344395的博客 319

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

[pytest源码6]-pytest-pluggy源码总结

前言 在分析完pytest-pluggy部分后,在本篇进行一个总结。 个人拙见,有错请各位指出。 如果的我的文章对您有帮助,不符动动您的金手指给个Star,予人玫瑰,手有余香,不胜感激。 回顾一下pluggy Demo # -*- coding:utf-8 -*- from pluggy import PluginManager, HookspecMarker, HookimplMarke...

qq_38959715的博客 2293

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

pytest源码目录概要分析

帮忙多点点文章末右下角的“好看”支持下,也可以将本文分享到朋友圈或你身边的朋友,谢谢Pytest是什么?pytest是一个非常成熟的全功能的Python测试框架。怎么安装?pip inst...

开源优测 340

对kotti_image项目进行pytest测试操作实践(失败)

现在kotti 手工安装的时候是Kotti-2.0.10.dev0kotti_image手工安装的时候依赖kotti是Kotti-2.0.9而且类似sqlalchemy这样的,版本一弄就升到2.x,又要手工降低回1.4.36版...

skywalk8163的专栏 965
上一篇: Python常用内置函数(二)
下一篇: 【Android自动化】自动输入安装密码,并检测App是否安装成功
晴时初遇雨
博客等级 码龄7年 1253粉丝 72原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值