pytest多线程与多设备并发appium

1、appium+python 实现单设备的 app 自动化测试

  • 启动 appium server,占用端口 4723
  • 电脑与一个设备连接,通过 adb devices 获取已连接的设备
  • 在 python 代码当中,编写启动参数,通过 pytest 编写测试用例,来进行自动化测试。

2、若要多设备并发,同时执行自动化测试,那么需要:

  • 确定设备个数
  • 每个设备对应一个 appium server 的端口号,并启动 appium
  • pytest 要获取到每个设备的启动参数,然后执行自动化测试。

3、实现策略

第一步:从设备池当中,获取当前连接的设备。若设备池为空,则无设备连接。

第二步:若设备池不为空,启动一个线程,用来启动appium server.与设备个数对应。
起始server端口为4723,每多一个设备,端口号默认+4

第三步:若设备池不为空,则启用多个线程,来执行app自动化测试。

4、具体实现步骤

4.1 通过 adb 命令,获取当前已连接的设备数、设备名称、设备的安卓版本号。

定义一个 ManageDevices 类。

1. 重启adb服务。
2. 通过adb devices命令获取当前平台中,已连接的设备个数,和设备uuid.
3. 通过adb -P 5037 -s 设备uuid shell getprop ro.build.version.release获取每一个设备的版本号。
4. 将所有已连接设备的设备名称、设备版本号存储在一个列表当中。
5. 通过调用get_devices_info函数,即可获得4中的列表。

实现的部分代码为:

"""
@Title   : app多设备并发-appium+pytest
@Author  : 柠檬班-小简
@Email   : lemonban_simple@qq.com
"""

class ManageDevices:
    """
       1、重启adb服务。
       2、通过adb devices命令获取当前平台中,已连接的设备个数,和设备uuid.
       3、通过adb -P 5037 -s 设备uuid shell getprop ro.build.version.release获取每一个设备的版本号。
       4、将所有已连接设备的设备名称、设备版本号存储在一个列表当中。
       5、通过调用get_devices_info函数,即可获得4中的列表。
    """

    def __init__(self):
        self.__devices_info = []
        # 重启adb服务
        self.__run_command_and_get_stout("adb kill-server")
        self.__run_command_and_get_stout("adb start-server")

    def get_devices_info(self):
        """
        获取已连接设备的uuid,和版本号。
        :return: 所有已连接设备的uuid,和版本号。
        """
        self.__get_devices_uuid()
        print(self.__devices_info)
        self.__get_device_platform_vesion()
        return self.__devices_info

4.2 定义一个设备配置池。

设备启动参数管理池。
每一个设备:对应一个启动参数,以及appium服务的端口号。

1. desired_caps_config/desired_caps.yaml文件中存储了启动参数模板。
2. 从1中的模板读取出启动参数。
3. 从设备列表当中,获取每个设备的设备uuid、版本号,与2中的启动参数合并。
4. 每一个设备,指定一个appium服务端口号。从4723开始,每多一个设备,默认递增4
5. 每一个设备,指定一个本地与设备tcp通信的端口号。从8200开始,每多一个设备,默认递增4.
在启动参数当中,通过systemPort指定。
因为appium服务会指定一个本地端口号,将数据转发到安卓设备上。
默认都是使用8200端口,当有多个appium服务时就会出现端口冲突。会导致运行过程中出现socket hang up的报错。

实现的部分代码:

def devices_pool(port=4723,system_port=8200):
    """
    设备启动参数管理池。含启动参数和对应的端口号
    :param port: appium服务的端口号。每一个设备对应一个。
    :param system_port: appium服务指定的本地端口,用来转发数据给安卓设备。每一个设备对应一个。
    :return: 所有已连接设备的启动参数和appium端口号。
    """
    desired_template = __get_yaml_data()
    devs_pool = []
    # 获取当前连接的所有设备信息
    m = ManageDevices()
    all_devices_info = m.get_devices_info()
    # 补充每一个设备的启动信息,以及配置对应的appium server端口号
    if all_devices_info:
        for dev_info in all_devices_info:
            dev_info.update(desired_template)
            dev_info["systemPort"] = system_port
            new_dict = {
                "caps": dev_info,
                "port": port
            }
            devs_pool.append(new_dict)
            port += 4
            system_port += 4
    return devs_pool

特别注意事项:2 个及 2 个以设备并发时,会遇到设备 socket hang up 的报错。

原因是什么呢:

在 appium server 的日志当中,有这样一行 adb 命令:adb -P 5037 -s 08e7c5997d2a forward tcp\:8200 tcp\:6790

什么意思呢?

将本地 8200 端口的数据,转发到安卓设备的 6790 端口
所以,本地启动多个 appium server,都是用的 8200 端口,就会出现冲突。

解决方案:

应该设置为,每一个 appium server 用不同的本地端口号,去转发数据给不同的设备。
启动参数当中:添加systemPort= 端口号来设置。
这样,每个设备都使用不同的本地端口,那么可解决此问题。

4.3 appium server 启停管理 。

(ps 此处可以使用 appium 命令行版,也可以使用桌面版)

  • 在自动化用例运行之前,必须让 appium server 启动起来。
  • 在自动化用例执行完成之后,要 kill 掉 appium 服务。这样才不会影响下一次运行。

代码实现如下:

import subprocess
import os

from Common.handle_path import appium_logs_dir

class ManageAppiumServer:
    """
    appium desktop通过命令行启动appium服务。
    不同平台上安装的appium,默认的appium服务路径不一样。
    初始化时,设置appium服务启动路径
    再根据给定的端口号启动appium
    """

    def __init__(self,appium_server_apth):
        self.server_apth = appium_server_apth

    # 启动appium server服务
    def start_appium_server(self,port=4723):
        appium_log_path = os.path.join(appium_logs_dir,"appium_server_{0}.log".format(port))
        command = "node {0} -p {1} -g {2} " \
                  "--session-override " \
                  "--local-timezone " \
                  "--log-timestamp & ".format(self.server_apth, port, appium_log_path)
        subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,shell=True).communicate()

    # 关闭appium服务
    @classmethod
    def stop_appium(cls,pc,post_num=4723):
        '''关闭appium服务'''
        if pc.upper() == 'WIN':
            p = os.popen(f'netstat  -aon|findstr {post_num}')
            p0 = p.read().strip()
            if p0 != '' and 'LISTENING' in p0:
                p1 = int(p0.split('LISTENING')[1].strip()[0:4])  # 获取进程号
                os.popen(f'taskkill /F /PID {p1}')  # 结束进程
                print('appium server已结束')
        elif pc.upper() == 'MAC':
            p = os.popen(f'lsof -i tcp:{post_num}')
            p0 = p.read()
            if p0.strip() != '':
                p1 = int(p0.split('\n')[1].split()[1])  # 获取进程号
                os.popen(f'kill {p1}')  # 结束进程
                print('appium server已结束')

4.4 pytest 当中根据不同的启动参数来执行自动化测试用例

在使用 pytest 执行用例时,是通过 pytest.main()会自动收集所有的用例,并自动执行生成结果。

这种情况下,appium 会话的启动信息是在代码当中给定的。

以上模式当中,只会读取一个设备的启动信息,并启动与设备的会话。

虽然 fixture 有参数可以传递多个设备启动信息,但它是串行执行的。

需要解决的问题的是:

  • 可以传递多个设备的启动参数,但不是通过 fixture 的参数。
  • 每传递一个设备启动参数进来,执行一次 pytest.main()

解决方案:

  • 通过 pytest 的命令行参数。即在 pytest.main()的参数当中,将设备的启动信息传进来。
  • 使用 python 的多线程来实现。每接收到一个设备启动参数,就启动一个线程来执行 pytest.main

4.4.1 第一个,pytest 的命令行参数。

首先需要在 conftest.py 添加命令行选项,命令行传入参数”--cmdopt“。

用例如果需要用到从命令行传入的参数,就调用 cmdopt 函数。

def pytest_addoption(parser):
    parser.addoption(
        "--cmdopt", action="store", default="{platformName:'Android',platformVersion:'5.1.1'}",
        help="my devices info"
    )

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

@pytest.fixture
def start_app(cmdopt):
    device = eval(cmdopt)
    print("开始与设备 {} 进行会话,并执行测试用例 !!".format(device["caps"]["deviceName"]))
    driver = start_appium_session(device)
    yield driver
    driver.close_app()
    driver.quit()

4.4.2 使用多线程实现: 每接收到一个设备启动参数,就启动一个线程来执行 pytest.main

定义一个 main.py。

run_case 函数。

此方法主要是:接收设备启动参数,通过 pytest.main 去收集并执行用例。

# 根据设备启动信息,通过pytest.main来收集并执行用例。
def run_cases(device):
  """
  参数:device为设备启动参数。在pytest.main当中,传递给--cmdopt选项。
  """
    print(["-s", "-v", "--cmdopt={}".format(device)])
    reports_path = os.path.join(reports_dir,"test_result_{}_{}.html".format(device["caps"]["deviceName"], device["port"]))
    pytest.main(["-s", "-v",
                 "--cmdopt={}".format(device),
                 "--html={}".format(reports_path)]
                )
每有一个设备,就启动一个线程,执行 run_cases 方法。
# 第一步:从设备池当中,获取当前连接的设备。若设备池为空,则无设备连接。
devices = devices_pool()

# 第二步:若设备池不为空,启动appium server.与设备个数对应。起始server端口为4723,每多一个设备,端口号默认+4
if devices and platform_name and appium_server_path:
    # 创建线程池
    T = ThreadPoolExecutor()
    # 实例化appium服务管理类。
    mas = ManageAppiumServer(appium_server_path)
    for device in devices:
        # kill 端口,以免占用
        mas.stop_appium(platform_name,device["port"])
        # 启动appium server
        task = T.submit(mas.start_appium_server,device["port"])
        time.sleep(1)

    # 第三步:若设备池不为空,在appium server启动的情况下,执行app自动化测试。
    time.sleep(15)
    obj_list = []
    for device in devices:
        index = devices.index(device)
        task = T.submit(run_cases,device)
        obj_list.append(task)
        time.sleep(1)

    # 等待自动化任务执行完成
    for future in as_completed(obj_list):
        data = future.result()
        print(f"sub_thread: {data}")

    # kill 掉appium server服务,释放端口。
    for device in devices:
        ManageAppiumServer.stop_appium(platform_name, device["port"])

到此这篇关于pytest多线程与多设备并发appium的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Pytest自定义mark标记筛选用例

    如下图所示,我们编写的用例存放在不同的py文件当中 当我们想只运行诸多py文当中的部分用例,怎么办呢? 比如自动化工作当中,选择test_a,test_33,test_000这3个用例来运行的话,如何过滤呢? pytest.mark一下 在pytest当中,先给用例打标记,在运行时,通过标记名来过滤测试用例. 步骤1:给用例打标签 给用例打标记分为2个步骤: 1)注册标签名 官方提供的注册方式有2种,这里只提供一种最简单直接的方式: 通过pytest.ini配置文件注册.在pytest.ini文

  • Appium+Python+pytest自动化测试框架的实战

    菜鸟一枚,写的不好勿喷,大家一起学习 先简单介绍一下目录,再贴一些代码,代码里有注释 Basic目录下写的是一些公共的方法,Data目录下写的是测试数据,image存的是测试失败截图,Log日志文件,Page测试的定位元素,report测试报告,Test测试用例,pytest.ini是pytest启动配置文件,requirements.txt需要安装的py模块,run.py运行文件 Basic/base.py 里面封装了 一些方法,元素的点击,输入,查找,还有一些自己需要的公共方法也封装在里面,

  • Pytest框架之fixture详解(三)

    本文关于fixture的内容如下: 1.参数化fixture 2.fixture工厂 3.request这个fixture 1.参数化fixture fixture有个params参数,允许我们传递数据. 语法格式: # conftest.py文件 ​ # fixture的params参数 # 取value1时,会把依赖此fixture的用例执行一遍. # 取value2时,会把依赖此fixture的用例执行一遍. # 取value3时,会把依赖此fixture的用例执行一遍. # params

  • Pytest框架之fixture详解(一)

    我们在编写测试用例,都会涉及到用例执行之前的环境准备工作,和用例执行之后的环境清理工作. 代码版的测试用例也不例外.在自动化测试框架当中,我们也需要编写: 用例执行之前的环境准备工作代码(前置工作代码) 用例执行之后的环境清理工作(后置工作代码) 通常,在自动化测试框架当中,都叫做fixture. pytest作为python语言的测试框架,它的fixture有2种实现方式. 一种是xunit-style,跟unittest框架的机制非常相似,即setup/teardown系列 一种是它自己的f

  • python单元测试框架pytest介绍

    pytest是python语言中一款强大的单元测试框架,用来管理和组织测试用例,可应用在单元测试.自动化测试工作中. unittest也是python语言中一款单元测试框架,但是功能有限,没有pytest灵活. 就像:苹果电脑mac air和mac pro一样.都是具备同样的功能,但是好用,和更好用. 本文包含以下几个内容点: 1)pytest的简单示例 2)pytest的安装 3)pytest的特征.与unittest的区别. 4) pytest如何自动识别用例. 5)pytest框架中,用例

  • Pytest框架之fixture详解(二)

    本文关于 fixture 的内容如下: fixture 的 autouse 参数 session 和 module 级别的 fixture 1.fixture 的 autouse 参数 pytest 当中的 fixture, 默认情况下在定义好之后,需要测试用例/测试类主动请求使用,才会执行. 但是它有一个参数叫做 autouse,默认是 False. 关闭 fixture 的自动调用/自动执行功能. 如果设置 autouse=True,则表示这个 fixture 在它的作用域范围内都会自动化执

  • pytest多线程与多设备并发appium

    1.appium+python 实现单设备的 app 自动化测试 启动 appium server,占用端口 4723 电脑与一个设备连接,通过 adb devices 获取已连接的设备 在 python 代码当中,编写启动参数,通过 pytest 编写测试用例,来进行自动化测试. 2.若要多设备并发,同时执行自动化测试,那么需要: 确定设备个数 每个设备对应一个 appium server 的端口号,并启动 appium pytest 要获取到每个设备的启动参数,然后执行自动化测试. 3.实现

  • python并发编程之多进程、多线程、异步和协程详解

    最近学习python并发,于是对多进程.多线程.异步和协程做了个总结. 一.多线程 多线程就是允许一个进程内存在多个控制权,以便让多个函数同时处于激活状态,从而让多个函数的操作同时运行.即使是单CPU的计算机,也可以通过不停地在不同线程的指令间切换,从而造成多线程同时运行的效果. 多线程相当于一个并发(concunrrency)系统.并发系统一般同时执行多个任务.如果多个任务可以共享资源,特别是同时写入某个变量的时候,就需要解决同步的问题,比如多线程火车售票系统:两个指令,一个指令检查票是否卖完

  • Android线程池控制并发数多线程下载

    多线程下载并不是并发下载线程越多越好,因为当用户开启太多的并发线程之后,应用程序需要维护每条线程的开销,线程同步的开销. 这些开销反而会导致下载速度降低.因此需要避免在代码中直接开启大量线程执行下载. 主要实现步奏: 1.定义一个DownUtil类,下载工作基本在此类完成,在构造器中初始化UI线程的Handler.用于子线程和UI线程传递下载进度值. 2.所有的下载任务都保存在LinkedList.在init()方法中开启一个后台线程,不断地从LinkedList中取任务交给线程池中的空闲线程执

  • python多线程并发让两个LED同时亮的方法

    在做毕业设计的过程中,想对多个传感器让他们同时并发执行.之前想到 light_red() light_blue() 分别在两个shell脚本中同时运行,但是这样太麻烦了.后来学到了Python多线程,让程序并发执行. 下面具体介绍步骤: 两个led灯,一个蓝灯,一个红灯 蓝灯正极接13,负极接14 红灯正极接12,负极接14 下面是代码: #!/usr/bin/python # -*- coding: UTF-8 -*- import RPi.GPIO as GPIO import thread

  • Python高并发和多线程有什么关系

    "高并发和多线程"总是被一起提起,给人感觉两者好像相等,实则 高并发 ≠ 多线程 多线程是完成任务的一种方法,高并发是系统运行的一种状态,通过多线程有助于系统承受高并发状态的实现. 高并发是一种系统运行过程中遇到的一种"短时间内遇到大量操作请求"的情况,主要发生在web系统集中大量访问或者socket端口集中性收到大量请求(例如:12306的抢票情况:天猫双十一活动). 该情况的发生会导致系统在这段时间内执行大量操作,例如对资源的请求,数据库的操作等.如果高并发处理

  • 详解Java多线程与并发

    一.进程与线程 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位. 线程:是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的 资源. 虽然系统是把资源分给进程,但是CPU很特殊,是被分配到线程的,所以线程是CPU分配的基本单位. 二者关系: 一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域. 程序计数器:是一块内存区域,用来记录线程当前要执行的指令地址 . 栈:用于存储该线程的局部变量,这些局部变量是

  • iOS 如何高效的使用多线程

    一.多线程简述 线程是程序执行流的最小单元,一个线程包括:独有ID,程序计数器 (Program Counter),寄存器集合,堆栈.同一进程可以有多个线程,它们共享进程的全局变量和堆数据. 这里的 PC (Program Counter) 指向的是当前的指令地址,通过 PC 的更新来运行我们的程序,一个线程同一时刻只能执行一条指令.当然我们知道线程和进程都是虚拟的概念,实际上 PC 是 CPU 核心中的寄存器,它是实际存在的,所以也可以说一个 CPU 核心同一时刻只能执行一个线程. 不管是多处

  • iOS开发探索多线程GCD队列示例详解

    目录 引言 进程与线程 1.进程的定义 2.线程的定义 3. 进程和线程的关系 4. 多线程 5. 时间片 6. 线程池 GCD 1.任务 2.队列 3.死锁 总结 引言 在iOS开发过程中,绕不开网络请求.下载图片之类的耗时操作,这些操作放在主线程中处理会造成卡顿现象,所以我们都是放在子线程进行处理,处理完成后再返回到主线程进行展示. 多线程贯穿了我们整个的开发过程,iOS的多线程操作有NSThread.GCD.NSOperation,其中我们最常用的就是GCD. 进程与线程 在了解GCD之前

  • 科普:多线程与异步的区别

    随着拥有多个硬线程CPU(超线程.双核)的普及,多线程和异步操作等并发程序设计方法也受到了更多的关注和讨论.本文主要是想与园中各位高手一同探讨一下如何使用并发来最大化程序的性能. 多线程和异步操作的异同 多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性.甚至有些时候我们就认为多线程和异步操作是等同的概念.但是,多线程和异步操作还是有一些区别的.而这些区别造成了使用多线程和异步操作的时机的区别. 异步操作的本质 所有的程序最终都会由计算机硬件来执行,所以为了更好的理解异

  • Java 高并发九:锁的优化和注意事项详解

    摘要 本系列基于炼数成金课程,为了更好的学习,做了系列的记录. 本文主要介绍: 1. 锁优化的思路和方法 2. 虚拟机内的锁优化 3. 一个错误使用锁的案例 4. ThreadLocal及其源码分析 1. 锁优化的思路和方法 在[高并发Java 一] 前言中有提到并发的级别. 一旦用到锁,就说明这是阻塞式的,所以在并发度上一般来说都会比无锁的情况低一点. 这里提到的锁优化,是指在阻塞式的情况下,如何让性能不要变得太差.但是再怎么优化,一般来说性能都会比无锁的情况差一点. 这里要注意的是,在[高并

随机推荐