基于pytest-xdist结合appium实现多机分发用例自动化方案设计.
哈喽,大家好,好久没有发技术分享贴了,今天就带来一个近期我个人捣鼓的自动化多机下发用例的方案设计,主要针对做UI自动化的同学,看完之后会有比较不错的提升理解的,希望您读完之后可以吊打面试官,如果本文对您有所帮助,烦请一键三连,十分感谢.
一.需求背景
- 痛点分析: 大家在设计UI自动化的时候,经常会遇到单个自动化任务用例数量过多,导致单轮次执行的话耗时太久,单轮次需要24h左右才可以执行完,等待报告的时间过长,导致测试延期/不能够及时发现问题,比较耗费时间,那么能否实现通过多机的方式分发掉用例,比如使用三台手机来跑完这个专项,每台手机执行1/3的用例,最后整合成一个报告,即可极大地节约执行时间.
二.技术选型
三.方案设计
底层实现原理:
要实现多设备并发执行并按比例分配用例(如 4 条用例分给 2 台设备各 2 条),可以结合 pytest-xdist 的负载均衡 和 动态设备映射 实现。核心思路是让 pytest 自动将用例池分配到多个设备进程,无需手动标记,实现真正的动态分发。
1.pytest-xdist 是一个支持多进程执行测试的插件,允许将测试用例分布到多个 CPU 核心上并行执行,从而显著提升测试执行效率。
pip install pytest-xdist
2.打开pycharm新建一个项目,名称任意取,比如我这里交: more_device_function2
3.准备2台及以上的手机,这里我就拿Android机来举例,当然如果你使用虚拟机的话也应该可以,这鳄梨我没有试过,感兴趣的小伙伴可以试一下虚拟机,实体机的话不会有太大问题.
adb devices
4.开始配置设备,那么我这里演示的话是把设备的信息进行固定写死的,也就是说如果在公司里面使用的是固定的机型来跑自动化,那么就可以提前把对应设备的信息写在配置文件里面,这样的话脚本可以很快的读取,更好的方式是采用云真机平台的方式来维护设备信息,实现动态下发用例给设备.
-新建一个配置文件: device.yaml
这里通过yaml的格式来维护设备信息非常的清晰可见,当然你使用别的格式的如json等均可,看自己需求.
udid:即我们的手机序列号,在上述输入adb devices 的时候会打印,填入即可
port: 端口号,那么appium服务启动的时候会占用端口4723, 我们如果是多台设备就分配不同的端口好,比如这里我给到4723,4725.
systemPort: 这是uiautomator2的端口号,自动化驱动.
device_id: 设备名称, 取一个易于区分的名称即可.
5.新建配置文件conftest.py 配置文件的目的主要是读取全局配置,启动appium服务,动态分发设备,配置日志级别如默认info等.
def load_devices(): with open("device.yaml", "r", encoding='utf-8') as f: return yaml.safe_load(f)["devices"] devices = load_devices() device_count = len(devices) # 设备数量(此处为2) # 全局保存Appium服务(避免重复启动) appium_services = {}
6.启动appium服务.
def start_appium_service(device): port = device["port"] if port not in appium_services: service = AppiumService() service.start( port=port, args=[ f"--udid {device['udid']}", f"--system-port {device['systemPort']}", "--log-level info" ] ) appium_services[port] = service return appium_services[port]
7.动态分配设备,通过获取xdist的workerid来分配设备.
@pytest.fixture(scope="session") def device(request): try: worker_id = request.config.getoption("workerid") except ValueError: # 未使用xdist时,默认使用第一个设备 return devices[0] if devices else None # 处理分布式模式 if worker_id == "master": return None # 主进程不执行用例 # 转换进程索引并分配设备 worker_idx = int(worker_id.replace("gw", "")) assigned_device = devices[worker_idx % device_count] return assigned_device
8.分配驱动driver到每个设备上.
@pytest.fixture(scope="function") def driver(device, request): if not device: pytest.skip("主进程不执行用例") # 启动当前设备的Appium服务 start_appium_service(device) # 初始化driver desired_caps = { "platformName": device["platformName"], "udid": device["udid"], "appPackage": "com.example", # 替换为你的APP包名 "appActivity": ".app.SplashActivity", # 替换为正确的Activity "automationName": "UiAutomator2", "noReset": True } driver = webdriver.Remote( command_executor=f"http://127.0.0.1:{device['port']}", desired_capabilities=desired_caps ) # 记录当前用例在哪个设备执行(方便日志追踪) print(f"\n[执行设备] {device['device_id']} 执行用例: {request.node.name}") yield driver driver.quit()
9.用例层: 新建文件test_cases.py 用来编写测试用例,这里给了4条测试用例.
import logging import time def test_case1(driver): """测试用例1""" logging.info("测试用例1") assert True def test_case2(driver): """测试用例2""" logging.info("测试用例2") assert True def test_case3(driver): """测试用例3""" logging.info("测试用例3") assert True def test_case4(driver): """测试用例4""" logging.info("测试用例4") assert True
四.如何运行
1.首先启动appium服务,打开控制台,输入 appium ,出现如下日志打印即为正常.
2.进入项目目录,脚本启动命令如下: -n 指定设备运行数量, -v -s都是pytest自带的功能,不理解的同学需要去学习一下pytest的教程,或者dd~我领取也可.
pytest .\test_cases.py -n 2 -v -s --alluredir workspace/allure-results
运行结果: 配置了2台设备(当前调试脚本写死),4条用例,实现用例分配到不同设备,gw0,gw1代表不同设备.
5条用例情况,可以看到用例分配到了2台设备在执行,实现了用例下发的场景.
生成测试报告->得益于上述命令: --alluredir workspace/allure-results
五.结语
对于当前设计方案有不理解的地方可以向俺询问,其中这这是一个简单的demo演示案例,像手机的配置信息不应该通过配置文件写死,应该通过云真机的方式平台化来维护识别,当然这个场景在很多公司是通过平台化的方式来分发用例,像动态计算用例执行耗时应该也有一个函数来处理,由于只是演示阶段,做了简化处理,这个实现起来并不是很难,感兴趣的小伙伴可以在次方案基础上增加次功能,这里不再赘述.
主要分享一些测试开发方向提效,提质内容:测开进阶,测试工具开发,测试平台开发,录制回放,测试报告,精准测试研究等.