iOS 测试 使用 Airtest 实现多台 IOS 真机的并发测试

Rita · July 30, 2020 · Last by TD replied at August 06, 2020 · 1331 hits

最近在学习Airtest,主要用于进行多台ios真机设备的自动化兼容性测试

1、背景

Airtest是网易出品的一款基于图像识别和poco控件识别的一款UI自动化测试工具。Airtest的框架是网易团队自己开发的一个图像识别框架,这个框架的祖宗就是一种新颖的图形脚本语言Sikuli。Sikuli这个框架的原理是这样的,计算机用户不需要一行行的去写代码,而是用屏幕截屏的方式,用截出来的图形摆列组合成神器的程序,这是Airtest的一部分。

另外,Airtest也基于poco这个UI控件搜索框架,这个框架也是网易自家的跨平台UI测试框架,原理类似于appium,通过控件的名称,id之类的来定位目标控件,然后调用函数方法,例如click(),swipe()之类的方法来对目标控件进行点击或者是操作。

关于Airtest更详细的介绍请参考Airtest官方手册。https://airtest.doc.io.netease.com

2、环境搭建

测试环境:
MAC系统版本:macOS High Sierra 版本10.13.6
xcode10.1(添加了8-13.5.1的支持包)
ios真机若干

1)AirtestIED

脚本的录制使用的是AirtestIDE,AirtestIDE是一个跨平台的UI自动化测试编辑器,下载搭建都是非常简单的,跟随官网的指导即可 http://airtest.netease.com
若是操控单一设备,或运行单一脚本,用IDE操作非常方便与灵活。若是多机操作,及多脚本长时间操作,及使用其它的第三方库,还是脱离AirtestIDE,会更稳定与方便。

2)脱离AirtestIDE

脱离IDE的方法主要参考了0 https://www.jianshu.com/p/9f79f9486a15方法如下:

(1)安装并配置好python3.7
(2)安装第三方库:airtest与pocoui(使用pip3 install安装)
(3)检查cv2模块版本是否大于3.7,若不,运行以下命令:

pip3 uninstall opencv-contrib-python
pip3 install opencv-contrib-python==3.2.0.7(根据提示进行版本选择)
以上几步完成后,就可以脱离airtestIDE来编程了。

3)实现多台ios真机的并发测试

思路:给不同的设备分配不同的端口号,使用多进程的方式进行并发测试
关键代码片段:

#获取接入的手机的udid列表
sub = subprocess.Popen('idevice_id -l', shell=True, close_fds=True, stdout=subprocess.PIPE)
sub.wait()
udid = sub.stdout.read().decode().splitlines()
udid = list(set(udid))
#在终端执行wda的编译及端口转发,各手机操作之间需要加延时
for i in range(len(udid)):
proxy = str(8100+i+1)
#使用终端编译运行WDA,不同的手机的udid不同
os.system(xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination "id=udid[i]" test)
#给手机分配不同的端口号
os.system(iproxy proxy 8100 udid[i]')
#使用进程池的方式进行并发测试
pool.apply_async(
test_air, (proxy,udid[i]……))
……
def test_air(proxy,udid……):
#链接手机
auto_setup(__file__, logdir=udid_path, devices=[
"ios:///http://127.0.0.1:%s" %(proxy)])

#真正执行测试脚本
test_start()
#如果需要生成报告,执行
simple_report(__file__,logpath=xxx,output=xxx)

使用分配端口+多进程的方式,目前已经成功进行了多台设备的并发,说是并发,但考虑到WDA的编译,各手机之间的操作还是加了一些延时,目前测试是5-7台设备并行测试比较稳定。

3、测试脚本的优化

因为是兼容性测试,在一台手机上录制完了脚本之后,需要在多台不同分辨率的手机上进行回放,这就会降低脚本回放时点击的成功率,因此我们需要对脚本进行一些优化,来提升点击的成功率。
目前我想到的一些比较简单容易实现的办法总结如下(针对自己的业务需求,可能不适用于所有的小伙伴)

1)采用图像识别和poco混合使用的方式

比如:touch失败时,再使用poco来尝试点击,写为if else的形式(官网说明:目前IOS的poco模式不如Android效果好,因此可以将图像识别的方式放在前面);

#已修改了touch接口,添加了返回值
if not touch(Template(path+r"tpl1591927997240.png", record_pos=(0.376, -0.349), resolution=(1242, 2208))):
poco("扫一扫").click()

2)点击失败时,脚本执行不中断

因为我自己做的业务中,需要对app进行长时间的点击测试,中间漏掉某一项测试影响也不大,因此我使用了这个方式
针对图像识别的方式:修改swipe、touch(airtest库/usr/local/lib/python3/site-package中的core/api.py)接口,使图像和元素都匹配不到时,不抛出异常,继续向下执行,目前使用的是try...except方式;
针对poco方式:修改click、swipe等(poco下的proxy.py)接口,方法同上

3)添加全局等待时间

在进行脚本运行时,有时成功执行了点击操作,但页面还在加载中,就会使这一步操作跳过,从而引起下列的一系列错误,因此,最好在每一个操作后添加一个等待时间,即sleep。为方便,可通过添加全局变量的形式进行添加,如下:

from airtest.core.api import * 
# airtest.core.api中包含了一个名为ST的变量,即为全局设置

ST. OPDELAY= 3 #OPDELAY默认值为0.1,可以自定义
poco("扫一扫").click()

如果某些步骤响应时间很久,可以在语句后再单独添加一句sleep

4)适当降低图像识别阈值

图像识别阈值threshold是用来判定一张图片识别是否成功的阈值,例如一张图片识别到的匹配度是0.65,而我们设置的threshold为0.7的话,Airtest会认为匹配失败,从而进行下一次匹配。通常来说,threshold设置得越高,图像识别的精度越高,但成功率也会有所降低
如果希望修改全局所有图片的图像识别阈值THRESHOLD(也可以在语句中通过传参的方式单独改变阈值),在脚本中添加:

from airtest.core.api import *

ST. THRESHOLD= 0.6 # THRESHOLD取值范围为0~1之间,[0, 1],默认值是0.7

5)采用按屏幕比例进行swipe的方式

我们测试APP时,经常会有滑动操作,如果按照图片到图片,图片到指定坐标,坐标到坐标的方式进行滑动,换手机后成功率非常低,特别是屏幕大小相差较大的两款手机,基本无法同时成功执行同一个swipe指令。
因此,我采用了按照屏幕比例进行swipe操作的方式,操作前,先实时获取屏幕的尺寸,然后按照比例进行滑动或拖拽,代码如下:

def swipe_(width,height):
#获取屏幕尺寸,用于拖拽
start_pt = (width /2, height * 0.7)
end_pt = (width /2, height * 0.3)
if not swipe(start_pt, end_pt):
poco.swipe([0.5,0.6],[0.5,0.3])

因采用先图像识别后poco的方式,所以进行了简单封装,后续需要进行滑动操作时,直接调用swipe_( )即可

6)图像识别算法选择

关于airtest算法如何选择、性能的对比可以参考:
https://www.cnblogs.com/AirtestProject/p/12170016.html
目前脚本默认使用的是如下三种方式循环匹配,直至超时;

综合考虑性能、精确度、准确度等方面,目前采用的方式是比较合理的,因此没有进行改变,若在同一台手机上进行脚本录制回放,可以将TemplateMatch方式的优先级调制第一位,速度更快,方式如下:

from airtest.core.settings import Settings as ST     

ST.CVSTRATEGY = ["tpl", "sift","brisk"]

4、总结

时间有限写的比较仓促简略,用的方法也都比较简单粗暴,后续会再进一步的优化补充,欢迎有同样需求的小伙伴一起讨论,提出宝贵的意见建议(oo)

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 24 条回复 时间 点赞

os.system(iproxy proxy 8100 [udid]')这个应该是os.system(iproxy proxy 8100 [udid][i])把,这个环境应该是在win上的把? mac下好像要-u [udid]

Author only
Author only
Author only
Rita #5 · July 31, 2020 作者
#975 回复

是在mac下呢,不需要加u,写的不太严谨,已经修改,谢谢啦!

6Floor has been deleted
7Floor has been deleted
Rita #8 · July 31, 2020 作者
秦岭 回复

是不太稳定的,我目前试的各台手机加了10s延时,但是手机多了之后还是有部分手机无法运行起来,目前大概是5-7台比较稳定。我是用的图片识别优先,速度确实比android慢太多了,相比之下,poco觉得已经很快啦~

Rita #9 · July 31, 2020 作者
秦岭 回复

你说的这个任务分配,是指的十个用例,每个用例分给两个手机,然后20台手机并发测试的意思吗?

现在 ios的支持动态传入端口号吗?之前官网说ios-target只支持8100的端口号呢?

Rita #11 · July 31, 2020 作者
TD 回复

监测的是每台手机的8100端口号,但是往pc端转发的时候可以分配不同的pc端口号

秦岭 · #12 · July 31, 2020
Author only

老哥,你用的是买的证书吗

秦岭 · #14 · July 31, 2020
Author only
Rita #15 · July 31, 2020 作者
jiawei.li 回复

用的公司的账号,我是小姐姐 _ ^

Rita 回复

好吧,我在mac不带-u会报错

Rita #17 · July 31, 2020 作者

代码只贴了一些片段,是创建了一个进程池,这个里面自定义进程的数量,比如可以创建一个拥有十个进程的进程池。我是做兼容测试,就是几台手机同时跑一个用例。
你说的那种是不是可以把测试脚本当作模块来import和调用呢,然后通过传参的方式,让不同的手机调用不同的脚本来执行,目前我是把测试脚本当作模块来调用的,但是因为是兼容测试,所以就是多个进程同时调用同一个脚本模块
不知道咱们的业务需求是不是一样,我也是在学习和摸索阶段,目前还是能正常使用的,只是还有点击不准确的问题,还是需要优化的~~o^

我发现进程池速度没有自己创建多进程快
楼主可以尝试这个

from multiprocessing import Process


class InsertData(Process):
def __init__(self, process_num):
super().__init__()
self.process_num = process_num

def run(self):
your_method(self.process_num)


def your_method(process_num):
pass


# 实测发现速度快 进程池速度太慢
for x in range(5):
InsertData(x).start()
Rita #19 · July 31, 2020 作者
zhenyu 回复

感谢分享,我试一下你的办法~

秦岭 · #20 · July 31, 2020
Author only
TD · #21 · August 02, 2020
Author only
Rita #22 · August 04, 2020 作者
TD 回复

没太明白,你的意思是执行了xcodebuild然后执行不到iproxy?

Rita 回复

是的,os.system是堵塞的,换成subprocess.Popen这个可以。现在最主要的问题是我的端口号这样设置不行呢

Rita #24 · August 05, 2020 作者
TD 回复

我开了多个终端的,在一个里面os.system肯定不行~你先试试在不同的终端里面分别执行转发8100,8101这样行不行呢?

Hello ,想请问下楼主怎么解决ios弹框无法处理的问题

TD · #26 · August 06, 2020
Author only
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up