使用appium与python进行app的UI自动化测试
前言
对于自动化测试而言,UI 自动化测试相当于最后的验收测试环节。此时,其前置的单元自动化测试,接口自动化测试,接口集成自动化测试都是保障数据及逻辑层面的 正确性。而 UI自动化测试 则真正集成了用户端的真实操作,其结果也更贴近用户的感受。因此,也就更为重要。但由于电商互联网的快节奏改版和迭代,导致UI层面的 测试有时候会显得更为复杂,并且通常产品上线前留给测试的时间并不多,因此大多难以落地。但是,若UI相对稳固,那么使用UI自动化测试带来的好处不言而喻,对于重复性 测试来说,效率更高,扩充测试用例更为方便。长期而言,是非常值得推进的。
设计准则
一般而言,APP 的 UI 自动化测试 离不开 appium。同时,使用 python 的 pytest 作为测试框架。设计的准则是
- 框架需要封装并屏蔽Android 与 iOS 的代码级别的差异,即使用同一套python代码即可测试不同手机平台上的同一款app
- 框架需要封装android 与iOS 的 元素定位上的差异,目标是python代码不变动,而只需要配置各个手机平台的定位UI元素的方法
- 框架需要封装断言预期等数据到CSV文件,更简单的编写断言代码
- 编写新的测试用例的基本模板,其样板代码最好控制到最少(例如:10行之内)
测试框架
基于测试准则设计测试框架为:
appium + python + pytest + json 配置文件 + ini 配置文件 + csv 数据配置文件
其中
-
json 配置文件: 主要用于 appium WebDriver 的 配置,此处选用 json 配置文件的原因是代码异常简洁
-
ini 配置文件: 主要用于 android 与 ios 手机UI元素定位的字典定义(通常定义1次即可),此处选用 ini 配置文件的原因是大量书写时语法非常简洁,没有过多关键字干扰问题
-
csv 配置文件: 主要用于用户输入的数据及预期数据(断言数据)的定义,此处选用 csv 配置文件的原因是格式简单,且以后可能会与jmeter接口集成测试的csv 配置文件合并起来
基础环境搭建
appium 的安装
到 appium 站点 下载最新的 appium desktop , 目前我用的是 https://github.com/appium/appium-desktop/releases/tag/v1.22.3-4 这个版本。 下载安装即可。 我使用的是mac版本,安装后打开的界面如下: 使用的时候,点击 [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 运行环境是否真的已经准备好了。
放一张截图:
$\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
- 安装python3,到官网下载安装即可。https://www.python.org/
- 安装 Appium-Python-Client
pip install appium-python-client
- 安装 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 自动化测试完成。