前言

对于自动化测试而言,UI 自动化测试相当于最后的验收测试环节。此时,其前置的单元自动化测试,接口自动化测试,接口集成自动化测试都是保障数据及逻辑层面的 正确性。而 UI自动化测试 则真正集成了用户端的真实操作,其结果也更贴近用户的感受。因此,也就更为重要。但由于电商互联网的快节奏改版和迭代,导致UI层面的 测试有时候会显得更为复杂,并且通常产品上线前留给测试的时间并不多,因此大多难以落地。但是,若UI相对稳固,那么使用UI自动化测试带来的好处不言而喻,对于重复性 测试来说,效率更高,扩充测试用例更为方便。长期而言,是非常值得推进的。

设计准则

一般而言,APP 的 UI 自动化测试 离不开 appium。同时,使用 python 的 pytest 作为测试框架。设计的准则是

  1. 框架需要封装并屏蔽Android 与 iOS 的代码级别的差异,即使用同一套python代码即可测试不同手机平台上的同一款app
  2. 框架需要封装android 与iOS 的 元素定位上的差异,目标是python代码不变动,而只需要配置各个手机平台的定位UI元素的方法
  3. 框架需要封装断言预期等数据到CSV文件,更简单的编写断言代码
  4. 编写新的测试用例的基本模板,其样板代码最好控制到最少(例如:10行之内)

测试框架

基于测试准则设计测试框架为:

appium + python + pytest + json 配置文件 + ini 配置文件 + csv 数据配置文件

其中

  1. json 配置文件: 主要用于 appium WebDriver 的 配置,此处选用 json 配置文件的原因是代码异常简洁

  2. ini 配置文件: 主要用于 android 与 ios 手机UI元素定位的字典定义(通常定义1次即可),此处选用 ini 配置文件的原因是大量书写时语法非常简洁,没有过多关键字干扰问题

  3. csv 配置文件: 主要用于用户输入的数据及预期数据(断言数据)的定义,此处选用 csv 配置文件的原因是格式简单,且以后可能会与jmeter接口集成测试的csv 配置文件合并起来

基础环境搭建

appium 的安装

appium 站点 下载最新的 appium desktop , 目前我用的是 https://github.com/appium/appium-desktop/releases/tag/v1.22.3-4 这个版本。 下载安装即可。 我使用的是mac版本,安装后打开的界面如下: images/appium/appium-1.png 使用的时候,点击 [startServer] 即可。

需要注意的是:测试android 应用需要配置 android sdk 的本地路径。具体点击[editConfigurations] 配置并保存重启appium即可。

下面重点来了,由于 使用appium不仅仅需要 appium desktop , 更需要一些配套设施,那些依赖需要单独手动安装。那么具体需要安装哪些依赖呢?

appium-doctor

这是一个npm 插件,要运行它,你需要 nodejs 环境,因此我本机安装了最新的nodejs . 之后,你可以

npm install appium-doctor

或者国内先安装 cnpm后再用 cnpm 安装 appium-doctor,解决 npm 安装时下载巨慢的问题

npm install cnpm
cnpm install appium-doctor

然后你就可以使用appium-doctor来检查你的appium 运行环境是否真的已经准备好了。 放一张截图: appium-doctor

$\color{#FF0000}{>注意<}$: 上面appium-doctor的截图中,necessary 部分是必须的,optional 部分是可选。理论上,必须的部分必须有,可选的部分看情况。对于截图或录视频等高级功能,最好可选部分都要安装好。一旦某一个依赖安装好了,那么就是打绿勾,否则会打红叉。 安装这些依赖,有的是 ruby的,有的nodejs的,还有的是shell命令的,mac上还要用到 brew ,确实比较麻烦,不一一讲述了。重要的是一定要知道appium-doctor。至少,necessary 部分都安装好。 由于我需要测试的是ios , 故而 optional 中的大部分都安装好了。

IOS 测试时 WebAgent 的安装

由于ios应用的特殊性,需要使用xcode相关工具,打开appium desktop for mac 所附带的 WebAgent 修改其中的 开发者信息,填入你自己的个人开发者账号。 具体为使用xcode 打开/Applications/Appium Server GUI.app/Contents/Resources/app/node_modules/appium/node_modules/appium-webdriveragent/WebDriverAgent.xcodeproj 这个工程文件。 修改完开发者账号,菜单点击 [product] -> [Build],构建成功后,点击[product] -> [Test],而此时你的手机应该通过数据线连接在mac电脑上。 如果Test 运行成功,而手机上没有反应,那么你可能需要信任该app的运行。手机上(我的是 ios 15.7) [设置] -> [通用] ->[VPN与设备管理],进去后信任开发者APP。再次重复上面的 Build -> Test 步骤就可以了。

此时 WebAgent 在手机上启动成功,电脑端启动 appium desktop 并点击 [startServer] 按钮。此时手机即可与电脑端进行WebDriver协议的通讯了。

app ui 元素的定位

app ui 元素的定位需要借助两个工具实现,一个是由阿里出品的 webeditor,详细地址是:https://github.com/alibaba/web-editor

另外一个是 appium 官方出品的辅助工具 appium-inspector,详细地址是: https://github.com/appium/appium-inspector

可以都下载并安装好。原因是各自有其优点,需要互为补充才行。

启动appium desktop 后就可以使用ui元素定位工具来定位各个ui 元素的位置了。

元素定位上的差异

对于 android 应用来说,app上所有的部件都是有id的,因此使用id进行定位是最为方便快捷的,效率最高。 而对于 ios 应用来说则没那么幸运,从目前测试结果来看,xpath的方式是支持的最好的。因此定位同一个位置元素,需要是用配置文件的方式,来屏蔽python代码级别上的差异,最简单的方式自然是为android 和ios各自提供一套具有相同key值的字典。因此本文使用 ini 配置文件作为这个字典的具体实现。例如,我的ios应用的ini 配置文件:

[首页]
首页=//XCUIElementTypeButton[@name="优选"]
我的=//XCUIElementTypeButton[@name="我的"]
首页专题场1=//XCUIElementTypeImage[@name="imagePlaceholder"]/XCUIElementTypeOther/XCUIElementTypeCollectionView/XCUIElementTypeCell/XCUIElementTypeOther/XCUIElementTypeImage

[首页专题场1]
返回首页=(0.071, 0.082)

[我的]
我的邀请码=//XCUIElementTypeStaticText[@name="邀请码: ${invite_code}"]
设置=//XCUIElementTypeButton[@name="mine setting"]

[设置]
退出登录=//XCUIElementTypeButton[@name="退出登录"]

[登录]
手机登录=//XCUIElementTypeStaticText[@name="手机登录"]
短信登录=//XCUIElementTypeStaticText[@name="短信登录"]
手机号框=//XCUIElementTypeApplication[@name="巨星优选"]/XCUIElementTypeWindow/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther[3]/XCUIElementTypeTextField
短信验证码框=//XCUIElementTypeApplication[@name="巨星优选"]/XCUIElementTypeWindow/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeOther/XCUIElementTypeScrollView/XCUIElementTypeOther[1]/XCUIElementTypeTextField
登录按钮=//XCUIElementTypeButton[@name="登录"]

由于 appium 在 ios 上是利用 苹果的 XCUITest 实现的远程控制功能,因此元素的xpath都是XCUI开头的。

使用 python 开发自动化UI测试

python 的代码比较简洁,基本上有编程基础的人,3天上手完全没问题。 自动化测试需要 python3 ,pytest 以及 appium client for python

  1. 安装python3,到官网下载安装即可。https://www.python.org/
  2. 安装 Appium-Python-Client
pip install appium-python-client
  1. 安装 pytest
pip install pytest

pytest 配置及代码

对于 pytest 来说有个重要文件 conftest.py, ${red}{注意 }$ 这个文件名是固定的,一般情况下放在项目的根路径下作为全局配置。

我需要在运行测试用例的时候,通过命令行来指定要测试的是android应用还是ios应用,此处的实现就至关重要, 代码如下:

import logging
import os
import sys

import pytest
# 注意此处代码修复 pytest 无法加载自定义模块的问题,该行代码加在conftest.py中则全局有效,其他文件中则无需再次写如下这行代码
sys.path.append(os.path.split(os.path.abspath(os.path.dirname(__file__)))[0])
from common.base.command import Command


def pytest_addoption(parser):
    # 该命令行参数指定运行的app类型
    parser.addoption("--app-type", action="store", default="android",
                     help="运行测试用例的app类型,可取值:android,ios")
    # 该命令行参数允许指定是否使用全局配置
    parser.addoption("--global-ui-config", action="store", default="true",
                     help="运行测试用例时是否使用全局UI配置文件")
    logging.basicConfig(level=logging.DEBUG)


@pytest.fixture
def cmd(request) -> Command:
    """ 对于 测试用例的同名参数将采用 夹具注入的方式提供该数据 """
    c = Command()
    c.AppType = request.config.getoption("--app-type")
    c.GlobalUiConfig = True
    
    v = request.config.getoption("--global-ui-config")
    if v != "":
        if v.lower() == "true" or v.lower() == "yes":
            c.GlobalUiConfig = True
        else:
            c.GlobalUiConfig = False
    logging.info(f"\n正在初始化测试环境[{c.AppType}],GlobalUiConfig={c.GlobalUiConfig}...\n")
    return c

将命令行参数封装到一个 Command类中,方便以后的扩展。

编写简单测试用例

经过一系列封装操作,自定义了一系列 common 的包与辅助类,屏蔽了底层 android 和ios上的差异,写一个最简单的测试用例,test_main.py

from time import sleep

from common.base.app import App
from common.base.command import Command
from common.base.config import Config
from common.base.proxy import Proxy


def process(app: App, proxy: Proxy):
    # 点击 首页 中的首页按钮
    app.click_ui("首页.首页")
    # 点击 首页 中的 首页专题场1 图片链接
    app.click_ui("首页.首页专题场1")
    # 点击 首页专题场1 的页面中的 返回,回退到首页。
    app.tap("首页专题场1.返回首页").back()

    # 延时等待观看效果
    sleep(5)


class TestMain:

    def test_process(self, cmd: Command):
        app = App(Config(__file__, cmd))
        app.each_data(process)

app 的 ui操作封装在common.base.App中,预期及断言数据封装在 common.base.Proxy中,命令行命令封装在 common.base.Command中。 至此一个最简单的(但是扩展性非常好) UI 自动化测试完成。