iOS的自动化测试框架可分为两种:注入式和非注入式。注入式的框架通常会提供一些Lib或者是Framework,要求测试人员在待测应用的代码工程中导入这些内容,框架可以通过他们完成对app的驱动,典型的比如monkeytalk。非注入式的框架则是通过苹果提供的instruments工具,调用官方的接口函数,实现对app的相关操作,典型的就是uiautomation,appium
区别:
注入式:可获取app内的数据;可操作空间大,不受官方限制;可在windows平台进行测试;但是需要在待测项目中增加第三方的部分,使得测试的内容和实际发布的内容并不一致
非注入式:待测内容和最终的上线内容保持一致;测试无需源码;但是受官方限制,一些功能无法实现,而且环境要求必须使用os
x平台的Xcode。
采用instruments的原因:
1. 测试工具和测试文档均由苹果官方维护
2. 无需额外前置工作,可以直接对提测的app进行测试
3. 支持录制
1. 环境
mac:
OS X
Xcode-instruments
其中,Xcode通过mac上的App store即可直接安装使用,instruments为Xcode自带的工具集,无需单独下载
iPhone/iPad:
待测app
其中,待测app如果是运行在真机上,则一定需要用开发者证书签名,采用发布证书或者是企业证书打包的应用无法用以真机测试。
2. 准备工作 for 真机
1)手动安装应用到iPhone上,并连接iPhone到mac。
PS:如果该设备是第一次连接这台mac,需要等待organizer完成识别和同步工作才能使用。
PPS:如果organizer没有自动启动,可以通过Xcode->window->organizer,手动启动该程序。
2)启动instruments工具集,并选择Automation,进入测试工具的主界面
启动方式:打开Xcode,选择Xcode->Open DeveloperTool->Instruments
PS:启动instruments之后,可以在下方Doce菜单中,选中instruments的图标,右键选择在Dock中保留,便于下次快捷启动该工具,不用每次都去启动Xcode。
3. 准备工作 for 模拟器
方式一:从源码直接启动instruments,适用于有源代码工程的情况。
1) 双击*.xcodeproj 文件打开工程
2) 在Xcode中,选则Produce->Profile,该命令会首先build整个工程,在成功之后,自动启动instruments工具集,然后手动选择Automation工具即可。
方式二:使用已经编译完成的并且可用于模拟器运行的*.app文件,有无源码均可
1) 手动启动instruments工具集并进入Automation,选择待测的*.app文件即可
PS:运行于模拟器和真机的程序需要采用不同的方式进行编译,不能通用的。平时的提测包都是用于真机运行的,无法在模拟器运行。
2) 如果有源码,可以在选择模拟器和对应的iOS版本之后,直接Run到模拟器中,然后在需要测试时,通过Automation选择到模拟器目录下的该app文件也可以。
模拟器中的应用可以在这个路径下面找到:
/Users/用户名/Library/Application Support/iPhone Simulator/iOS版本/Applications/ |
该路径下载就是模拟器中的已安装的所有应用程序,找到待测试的*.app即可。
目前我测试的是一块视频应用,播放器底层的一些库运行在模拟器上会有问题,所以我的后面的所有内容都以运行于真机的环境为准。
4. 从录制开始
1) 选择待测试的应用。
在左上方的choose target上点击,选择当前连接的设备,以及该设备上已安装的待测试app
2) 聚焦到script区域,点击该区域下方的红色按钮
如果上一步选择的设备和app正确的话,就可以看到iPhone或者iPad上的应用被启动起来了(如果之前就已经启动了,Automation工具会先结束掉设备上的进程并重新启动)
应用启动完成之后,就正式开始录制模式了,此时在真机上对应用进行的任何操作,都会被录制成脚本的形式显示在script区域。由于用户操作和脚本录制同时在进行,所以此时的操作通常都会呈现出响应慢或者卡顿频繁的情况。
下面这段是登录帐号a然后注销帐号的一系列操作的录制结果
点击停止按钮,终止录制过程,然后点击回放按钮,开始回放已经录制好的脚本,我们可以看到应用重启并执行之前的一系列操作。
到目前为止,对操作的录制就算基本完成了,在script区域右键export即可以到处录制的js脚本。需要的时候,在脚本管理区域scripts通过点击add->Import的方式,导入js脚本并运行即可。
但是上面的脚本存在很多问题。
1) 脚本中有一行绿色的提示信息://Alert detected……
2) 实际执行会发现最后一行代码会出错。
3) 如果wifi的网络状况不是很稳定,这段脚本基本都会以失败告终。
4) 每行代码都很长,并且不利于阅读
5) 没有检查点的测试脚本都是在耍流氓
这些问题,我们在接下来一一解决。
5. 自己动手,丰衣足食
录制的代码可维护性和健壮性都很差,并且缺少必要的检查点,所以实际效果非常差。这时就需要自己编写测试代码,采用按需定制的方式来实现自动化。
首先一定要记住这个地址:
https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html |
这是苹果官方的参考文档,列举出了UI Automation JavaScript library的所有类,对app的各个操作最终都是通过这些api来实现的。
自动化的常见步骤通常是三步:定位,操作,检查
1) 定位元素
UIAutomation通过层级访问的方式,定位到某一个具体元素。苹果提供了一个logElementTree()的方法,打印控件树。在新建的测试脚本中,加入下面这行代码:
Target.logElementTree();
执行这段代码(点击左上角的红色按钮),我们可以看到输入了下面的东西
【截图】
上图就是整个app的UI层级结构,当你需要定位到某个元素的时候,可以按照这个层级一层一层的往下访问,直到目标元素。比如需要定位到播放记录。
var target= UIATarget.localTarget();
var app =target.frontMostApp();
var window= app.mainWindow();
//进入登陆界面
varloginButton = window.scrollViews()[0].buttons()["请点击登录"]; |
其中:
UIATarget 对象代表待测应用所在环境的最高层级UI,在这里localTarget()表示运行app的这台iPhone设备
UIAApplication对象代表app层级的UI,这里通过frontMostApp()方法得到的对象,就是指正在运行的影音iPhone
app。
UIAWindow对象代表app中window层级的UI,这里通过mainWindow()方法得到的对象,指当前app中的主窗体,一个app的当前界面通常只会有一个主窗体。
实际项目中,不同元素的差异都是从window层级开始的,在window层级往上,都是一样的。
2) 操作元素
上面一部完成就已经可以做到元素的定位,现在需要对这个元素进行操作,最常见的就是tap
对于不同的元素,提供的具体操作方法会存在差异,详情需参考官方文档。进入登录界面后,需要出入帐号密码,也是同样的步骤:
//定位输入框
varnameField = window.textFields()[0];
varpwField = window.secureTextFields()[0];
//操作
nameField.setValue("1@1.pp");
pwField.setValue("ppp111");
//点击执行登陆
window.buttons()["loginlogin button"].tap(); |
3) 检查结果
操作完成后,需要检查结果是否符合预期。
if(window.scrollViews()[0].staticTexts()[0].name() == "请叫我雷锋"){
UIALogger.logPass("测试通过");
}else{
UIALogger.logMessage(window.scrollViews()[0].staticTexts()[0].name());
UIALogger.logFail("测试失败");
} |
上述的这几个步骤,就算完成了UIAutomation的hello world,但是前面抛出的问题并没有完全解决。
6. 逐个击破
1)延迟
上面这段代码,在实际运行时,基本是会一直测试失败的,原因是从开始登录,到展示用户信息,需要一定的时间,点击登录按钮操作之后,立刻去判断用户信息也就一定会失败的。另外,网络波动,或者是app/测试机可能出现的卡顿,是造成测试脚本的频繁失败的原因之一,因此我们需要某些操作之后,人为增加一些必要的延迟,等待操作的完成,从而增强代码的稳定性。苹果在target层提供了一个方法delay(Number
timeInterval),该方法用于延迟脚本的执行,我们可以在必要的地方增加该方法。
2)弹框
弹框alert在iOS中比较特殊的类型,它不属于app层级的,也不能像操作普通元素一样去操作alert,苹果提供了一个专门的处理方法,UIATarget.onAlert,当弹框出现时,测试引擎会自动调用这个方法来处理弹框。
【待完善】
3)Log
UIALogger主要就用于输出各种类型的日志。包括logStart,logPass,logFail,logMessage,logDebug,logWarning
,logError
前三个通常用来区分一个测试用例,logstart表示一个测试的开始,直到logPass或者是logFail为止。后四个用以在测试过程中输入不同级别的日志。
7. tuneupjs
tuneupjs是一个用以优化uiautomation的第三方js库,网站地址: http://www.tuneupjs.org/
1)测试用例
Tuneupjs提供了test方法,用以表示一个测试用例,该方法有两个参数:测试用例的名称和一个测试内容的function,在这个方法中,你无须使用logStart,logPass,logFail这一类的方法,Tuneupjs已经在test方法的实现中,做了相应的处理,一个test方法就对应一条测试用例,当这个test方法中的function正常的执行完毕,这个case就会变为pass,如果执行过程中有任何异常抛出,test就会停止并把这个case置为fail。
test("登录测试",function(target,app){
app.tabBar().buttons()[2].tap();
var loginButton =window.scrollViews()[0].buttons()["请点击登录"];
//如果已登录则先注销账号
if (!loginButton.isVisible()) {
window.scrollViews()[0].buttons()[1].tap();
window.buttons()["退出登录"].tap();
target.delay(1);
}
//如果未登录则执行登录
if (loginButton.isVisible()) {
log("登录帐号*****");
loginButton.tap();
var nameField =window.textFields()[0];
var pwField =window.secureTextFields()[0];
nameField.setValue("*****"); //帐号
pwField.setValue("*****"); //密码
window.buttons()["login loginbutton"].tap();
target.delay(3);
}
assertEquals(window.scrollViews()[0].staticTexts()[0].name(),"请叫我雷锋","登录失败");
}); |
采用test方法来组织测试用例有一个很明显的好处,该方法已经做好了异常的处理,出现的任何异常都会被当前所在的test方法捕获到,而不会把异常抛给最上层,避免了一旦出现异常就会导致整个测试停止执行的情况。一个test失败之后,不会影响下一个test的执行。
2)断言
这是Tuneupjs的又一个优势,它提供了一系列的断言方法,可以用以对测试结果进行判断,并且在断言失败时自动截图保存,基础的断言方法包括assertEquals
assertNotEquals assertTrue assertFalse assertNull assertNotNull等,在一个test中,一旦出现断言失败,这条用例就会变为fail状态。
3)Retry方法
这是一个重试方法,配合断言使用效果更佳。retry()的必要参数就是一个function,如果function中出现了断言失败,或者是有异常抛出,该方法会再次尝试执行这个function,直到function的内容全部通过或者是最终超时。默认的重试次数是3次,每次重试间隔0.5秒,可以为retry()方法设置第二和第三个参数来修改这两个值。retry()方法比单纯的使用官方的delay()方法要更加合理。
retry(function(){
assertEquals(window.scrollViews()[0].staticTexts()[0].name(),"请叫我雷锋","登录失败");
},5,1); |
4)Test方法中手动抛出异常
有时候出于需要,我们需要在某个测试用例中手动抛出异常,让用例失败,可以使用tuneup提供的fail(message)方法,也可以自己直接抛异常throw(exception),test方法捕获异常之后会认为当前的用例失败,跳出并执行下一条用例。
5)命令行启动UIAutomation
UIAutomation本身是支持命令行启动的,也即非gui的模式。但是原生的命令非常的冗长,Tuneupjs对相关的命令做了封装,采用了自己的runner,并且支持输出xml格式的测试结果,可以用于jenkins等持续集成工具。
./UI_Test/alexvollmer-tuneup_js-5a4346d/test_runner/runiCloudPlayIPhone UI_Test/login.js
test-Result/-x –a 5
|
-x 表示输出一份xunit格式的xml
-a 5 表示启动instruments失败之后,重试启动的次数 |