appium+pytest+allure+jenkins 如何实现多台手机连接

使用 appium 可以实现 app 自动化测试,我们之前是连接一台手机去运行,如何同时连接多台手机呢?很多人可能想到的是多线程(threading)。今天分享一种比多线程更简单的方法,虽然不是多台手机同时运行,但可以连接多台手机依次运行,大致的运行方式是:001 号测试用例:A 手机,B 手机...,002 号测试用例:A 手机,B 手机...

环境准备

框架改造

1. 配置改写

以上课所写的前程贷的自动化框架为主,框架的分层如下(框架分享 - 传送门:提取码: zvry):

image.png

在上述框架中,我们的配置信息存在 Caps 目录下的 Caps.yaml 中,譬如这样

-
  platformName: Android
  platformVersion: 5.1.1
  deviceName: JTG6T16307007427
  appPackage: com.xxzb.fenwoo
  appActivity: .activity.addition.WelcomeActivity
  noReset: True

-
  server_ip: 127.0.0.1
  server_port: 4723

这只是一台手机的设备信息和连接信息,如果我们需要两台甚至多台,就需要把它们的信息都写入 YAML 文件,每个手机的信息用一个列表描述

-
  deviceDesc: Honor_5C
  server_url: 127.0.0.1
  server_port: 4723
  desired_caps:
    platformName: Android
    platformVersion: 5.1.1
    deviceName: JTG6T16307007427
    udid: JTG6T16307007427
    appPackage: com.xxzb.fenwoo
    appActivity: .activity.addition.WelcomeActivity
    noReset: True

-
  deviceDesc: YeShen
  server_url: 127.0.0.1
  server_port: 4726
  desired_caps:
    platformName: Android
    platformVersion: 5.1.1
    deviceName: 127.0.0.1:62025
    udid: 127.0.0.1:62025
    appPackage: com.xxzb.fenwoo
    appActivity: .activity.addition.WelcomeActivity
    noReset: True

注意:

  1. 上述 YAML 文件中多了 deviceDesc 和 udid,前者是我们用来区分不同的手机,后者是 appium 用来区分不同的手机  2) 给不同的手机设置不同的端口,荣耀畅玩 5C 使用的是 4723,夜神模拟器使用的是 4726

2. BaseDriver 的改写

BaseDriver 是公共的 driver 类,通过读取 YAML 配置信息,生成并返回 driver 对象,其基本的传递路径是:Caps.yaml-->BaseDriver.py-->conftest:设置不同的 fixture,返回 driver,因此它是沟通配置信息和 conftest 的桥梁,既然配置信息变了,相应的 BaseDriver 的读取也要改变
改写前的 BaseDriver.py,具体代码如下:

import yaml
import os
from Common.dir_config import caps_dir
from appium import webdriver

class BaseDriver:

    def base_driver(self, automationName="appium", noRest=True):
        fs = open(os.path.join(caps_dir, "caps.yaml"))
        datas = yaml.load(fs)
        #通过判断automationName,来决定是否添加uiautomator2
        if automationName != "appium":
            datas[0]["automationName"] = automationName
        if noRest == False:
            datas[0]["noReset"] = False
        #连接appium server,并告诉其要操作的对象
        server = 'http://{0}:{1}/wd/hub'.format(datas[1]["server_ip"], datas[1]["server_port"])
        driver = webdriver.Remote(server, datas[0])
        return driver

由于是多台手机,每个手机的通过配置信息里的 deviceDesc 来区分,BaseDriver 类中的 base_driver 函数需要设置一个变量 device 来区别不同的手机,这个 device 是我们传入的,如果我们传入的是 device="YeShen",还是用之前的代码的话,得到将是全部的信息。有必要通过 if 判断筛选下,只取对应 device 的配置信息

import yaml
from appium import webdriver

class BaseDriver:


    def base_driver(self, device, automationName="appium", noReset=True):
        fs = open(r"D:\Program\Jenkins\workspace\APP_AutoTest\appium_AutoTest\Caps\Caps.yaml", encoding="utf-8")
        datas = yaml.load(fs)
        for i in datas:
            if device == i["deviceDesc"]:
                if automationName != "appium":
                    i["desired_caps"]["automationName"] = automationName
                if noReset == False:
                    i["desired_caps"]["noReset"] = False
                desired_caps = i["desired_caps"]
                driver = webdriver.Remote("http://{0}:{1}/wd/hub".format(i["server_url"], i["server_port"]), desired_caps)
                return driver

3. conftest 的改写

conftest 是比较关键的一部,因为它会调用 BaseDriver()类中的 base_driver()方法,以往我们的 conftest 是这样定义的:

#登录:无弹出框,不重置(保留状态)
@pytest.fixture
def default_login_driver():
    driver = BaseDriver().basedriver()
    is_welcome(driver)
    yield driver
    driver.close_app()
    driver.quit()

但是现在不同了,这个 basedriver()函数中必须传入一个 device 的实参,这个实参是从哪里获得的?pytest 的 fixture 为我们提供了一种参数化的操作,fixture 可以带入参数 params,依赖于这个 fixture 的一套测试会根据参数的不同运行多次,而被装饰函数中通过特殊的 request 对象来访问每个参数:request.param 访问的是列表中的每个元素

params=["Honor_5C", "YeShen"]

#登录:无toast弹框,不重置
@pytest.fixture(params=params)
def login_common_driver(request):
    driver = BaseDriver().base_driver(device=request.param)
    is_welcome(driver)
    yield driver
    driver.close_app()
    driver.quit()

4. allure 的使用

allure 可以设置不同的特性:allure.feature(功能点)、allure.story(子功能点)、with allure.step(步骤):、allure.attach(附件)等,结合 pytest,一个收集测试用例,一个生成测试报告。现在通过 allure 在测试用例中给测试报告增加一些特性

class TestLogin:

    #登录成功——手机号、密码正确
    @allure.feature("登录模块")
    @allure.story("登录成功")
    @pytest.mark.usefixtures("login_common_driver")
    def test_login_success(self, login_common_driver):
        with allure.step("正常登录"):
            IndexPage(login_common_driver).click_login()
            LoginPage(login_common_driver).input_phoneNumber(login_success_data["phoneNumber"])
            LoginPage(login_common_driver).input_passwd(login_success_data["passwd"])
            IndexPage(login_common_driver).click_later()
        with allure.step("获取昵称"):
            IndexPage(login_common_driver).click_me()
            nickName = UserInfoPage(login_common_driver).get_nickName()
        with allure.step("比对昵称"):
            assert login_success_data["check"] == nickName

启动多个 appium-server

之前有想过 python 代码执行 appium 命令的形式去自动启动 appium 服务,但通过 npm 或 cnpm 安装 appium 命令行都有报错,只能手动启动。很简单,打开两个 appium 客户端,一个设置端口为 4723,一个 4726,启动即可

image.png

配置 jenkins 任务

这方面的内容不做过多介绍,只看下构建和构建后的操作,其中 allure-results 是 pytest 运行测试用例生成的 XML 报告所在的目录,jenkins 上的 Allure Commandline 插件会自动解析 XML,生成对应的 HTML 报告

image.png

image.png

allure 测试报告

十分美观吧,右上角的 TREND 显示的是多次运行结果的趋势,第 7 次到第 19 次都是 5 个 fail,第 20 次 6 个 fail

image.png

除此之外,allure 报告的 Behaviors 功能中可以看到每个测试用例对应的测试步骤、功能、子功能等,测试报告还会标记出同一个测试用例是哪台手机执行的,如下图,TestLogin.test_login_errorPasswd[Honor_5C]代表的执行机是荣耀畅玩 5C

image.png

jenkins 面板也展示了多次运行结果的趋势

image.png

结语

pytest 中 fixture 的参数化虽然能够实现多台手机同时连接,但是运行并不是同时的,因为 request.param 读取参数列表是遍历读取的,所以造成了一个测试用例,手机 A 先执行,手机 B 后执行(假设 params=["手机 A", "手机 B"]),要想真正做到多台手机同时运行,就要用到多线程

8 回帖
请输入回帖内容 ...
  • YLOVEQ

    这个报告能以邮件形式发送出去么

  • 其他回帖
  • huahua

    这个必须置顶加精!!!!I like!

  • huahua
  • xiaojian

    不用发邮件。。
    这个是在线测试报告 。。
    可以直接在 jenkins 上看得到,也可以直接发一个报告链接。
    或者在邮件中发个截图就好啦。。

  • 查看更多回帖