测试大佬的压箱绝技:教你app 自动化测试如何实现多设备并发

news2025/2/26 2:05:31

appium+python

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

  • 启动 appium server,占用端口 4723

  • 电脑与一个设备连接,通过 adb devices 获取已连接的设备

  • 在 python 代码当中,编写启动参数,通过 pytest 编写测试用例,来进行自动化测试。

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

  • 确定设备个数

  • 每个设备对应一个 appium server 的端口号,并启动 appium

  • pytest 要获取到每个设备的启动参数,然后执行自动化测试。

实现策略

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

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

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

具体实现步骤

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

定义一个 ManageDevices 类。

  • 重启adb服务。 

  • 通过adb devices命令获取当前平台中,已连接的设备个数,和设备uuid.

  • 通过adb -P 5037 -s 设备uuid shell getprop ro.build.version.release获取每一个设备的版本号。 

  • 将所有已连接设备的设备名称、设备版本号存储在一个列表当中。

  • 通过调用get_devices_info函数,即可获得4中的列表。

实现的部分代码为:

"""
@Title   : app多设备并发-appium+pytest
@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

 2 、定义一个设备配置池。 

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

  • desired_caps_config/desired_caps.yaml文件中存储了启动参数模板。 从1中的模板读取出启动参数。 

  • 从设备列表当中,获取每个设备的设备uuid、版本号,与2中的启动参数合并。 

  • 每一个设备,指定一个appium服务端口号。从4723开始,每多一个设备,默认递增4

  • 每一个设备,指定一个本地与设备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
  1. 特别注意事项:

    2 个及 2 个以上设备并发时,会遇到设备 socket hang up 的报错。

  2. 原因是什么呢:

    在 appium server 的日志当中,有这样一行 adb 命令:

    adb -P 5037 -s 08e7c5997d2a forward tcp\:8200 tcp\:6790

  3. 什么意思呢?

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

  4. 解决方案:

    应该设置为,每一个 appium server 用不同的本地端口号,去转发数据给不同的设备。

启动参数当中:添加 systemPort= 端口号 来设置。这样,每个设备都使用不同的本地端口,那么可解决此问题。

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、pytest 当中根据不同的启动参数来执行自动化测试用例

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

图片

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

图片

图片

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

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

需要解决的问题的是:

  • 可以传递多个设备的启动参数,但不是通过 fixture 的参数。

  • 每传递一个设备启动参数进来,执行一次 pytest.main()

解决方案:

  • 通过 pytest 的命令行参数。即在 pytest.main()的参数当中,将设备的启动信息传进来。

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

5、特别注意

  • 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()

 

  • 使用多线程实现:
    每接收到一个设备启动参数,就启动一个线程来执行 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"])

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1148440.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码审计-锐捷EG易网关 cli.php 远程命令执行

首先登录到后台中(可以组合 锐捷EG易网关 管理员账号密码泄露漏洞) 关键部分代码为 使用 exec 函数执行传递的命令 构造payload: /cli.php?ashell notdelaytrue&commandid漏洞证明: 文笔生疏,措辞浅薄,望各位大佬不吝赐教…

【C语言】内存的动态分配与释放

🦄个人主页:修修修也 🎏所属专栏:C语言 ⚙️操作环境:Visual Studio 2022 ​ 目录 什么是内存的动态分配? 内存动态分配函数 🎏malloc() 🎏calloc() 🎏realloc() 动态内存释放函数 🎏free() 常见的…

轻量封装WebGPU渲染系统示例<5>-多重纹理(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/version-1.01/src/voxgpu/sample/MultiTexturedCube.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 2. 高频调用与低频调用隔离。 3. 面向用户的易用性封装。 4. 渲染数据和渲染机制分离。 …

【2023Mathorcup大数据】B题 电商零售商家需求预测及库存优化问题 python代码解析

【2023Mathorcup大数据】B题 电商零售商家需求预测及库存优化问题 python代码解析 1 题目 2023 年MathorCup 高校数学建模挑战赛——大数据竞赛赛道B:电商零售商家需求预测及库存优化问题电商平台存在着上千个商家,他们会将商品货物放在电商配套的仓库…

ChineseChess1 2023.10.29

中国象棋残局 中国象棋残局模拟器ChineseChess1 2023.10.29 原来圈粉丝,钓鱼,只要不要脸就OK!!

洛谷趣题【过河卒】参考题解

背景 今天逛洛谷才注意到这道题,原题连接【P1002 [NOIP2002 普及组] 过河卒 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)】 对于爱下棋的我来说,当然是必刷之题。 题意 小卒起始点在左上角(0,0)处,我们的程序将接收两个坐标&#xff1…

解决历史图片创建时间错误(批量修改文件创建时间)

最近在整理历史文件,发现很多历史图片,截图,微信拍照等途径创建的图片没有创建时间和修改时间,导致在相册时间轴错误。集中出现在整理的当天。 这些图片基本在文件名都含有创建时间,大多格式如下: 对于其中…

集成Swagger

基于knife4j集成swagger 集成的步骤 1添加依赖 2配置包扫描 3给每个Controller配置API 4给每个方法&#xff08;接口&#xff09;配置入参、返回值的说明 5针对入参的实体、返回类型VO做配置 添加依赖 <dependency><groupId>com.github.xiaoymin</groupId…

web前端JS基础-----制作进度条

1&#xff0c;参考代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><progress id"pro" max"100" value"0"></progress><scrip…

hive 函数使用详解

一、前言 在任何一种编程语言中,函数可以说是必不可少的,像mysql、oracle中,提供了很多内置函数,或者通过自定义函数的方式进行定制化使用,而hive作为一门数据分析软件,随着版本的不断更新迭代,也陆续出现了很多函数,以满足日常数据查询与分析的各种场景。 二、hive 函…

阻塞队列.

目录 ♫什么是阻塞队列 ♫什么是生产-消费者模式 ♫实现一个阻塞队列 ♫BlockingQueue ♫什么是阻塞队列 阻塞队列是一种特殊的队列&#xff0c;它除了具备队列的先进先出的特点外&#xff0c;还具有以下特点&#xff1a; ♩.如果队列为空时&#xff0c;执行出队列操作&#xf…

设计模式:责任链模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

上一篇《享元模式》 下一篇《解释器模式》 简介&#xff1a; 责任链模式&#xff0c;它是一种行为型设计模式&#xff0c;它将许多对象连接起来形成一条链&#xff0c;每个对象处理不同的请求&#xff0c…

腾讯云轻量应用服务器地域怎么选择比较好?

腾讯云轻量应用服务器地域怎么选比较好?腾讯云轻量应用服务器地域是指轻量服务器数据中心所在的地理位置&#xff0c;如上海、广州和北京等地域&#xff0c;如何选择地域&#xff1f;腾讯云百科txybk.com关于地域的选择建议就近原则&#xff0c;用户距离轻量服务器地域越近&am…

物联网AI MicroPython传感器学习 之 mpu6050六轴陀螺仪传感器

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; 一、产品简介 MPU6050是一款6轴运动传感器&#xff0c;它集成了3 轴MEMS 陀螺仪&#xff0c;3 轴MEMS加速度计&#xff0c;以及一个可扩展的数字运动处理器DMP&#xff08;Digital Motion Processor&#xf…

“破解我!“---160个CrackMe练习001-Acid buen.exe

文章目录 前言题目分析破解过程Serial/Name验证方式爆破注册机 追码 Serial验证 前言 想开个系列&#xff0c;160个Crackme的练习&#xff0c;这个在52pojie上有个精华帖总结&#xff0c;写的特别好&#xff0c;推荐&#xff01;写这个系列主要还是记录一下自己的学习记录&…

Apollo模块:开源配置管理的明日之星

Apollo模块 概述目录结构功能模块编译福利活动 主页传送门&#xff1a;&#x1f4c0; 传送 概述 目录结构 按照功能模块划分&#xff1a; |-cyber 消息中间件&#xff0c;替换ros作为消息层 |-docker 容器相关 |-docs 文档相关 |-modules 自动驾驶模块&#xff0c;主要的定位…

Android 主题 vs 样式

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、相关知识3.1 theme&#xff01; st…

YUV的红蓝颠倒(反色)的原因及解决

原因 UV排列反了。 比如说&#xff0c;NV21和YUV420SP的Y排列相同&#xff0c;UV则相反。给你YUV420SP&#xff0c;你当作NV21保存JPG&#xff0c;就会发生红蓝拿起。 解决办法 就是把UV互换一下。具体代码&#xff1a; NV21转YUV420SP的代码_nv21转yuv420格式-CSDN博客 …

[毕设记录]@开题调研:一些产品

我感觉产品能代表落地的一些实际应用&#xff0c;会和研究的角度有些差别&#xff0c;但是需求和兴趣往往是从现实中来的&#xff0c;在上一篇blog里面看外国blog的时候顺着搜搜到了很多国外的智慧校园chatbot解决方案 文章目录 Comm100streebomodern campusUniBuddy Comm100 …

git建仓库小记

git建仓库小记 1.新建远端git仓库2.新建本地仓库3.添加ssh key4.将本地仓库关联到远端5.push & pull 每次新建git项目的时候都要翻翻之前收藏的几篇帖子&#xff0c;索性自己汇总一下记录&#xff0c;以后一次粘贴搞定。 1.新建远端git仓库 这个比较简单&#xff0c;网页…