什么是ui测试? UI测试是一个测试组件,用于自动测试与UI的交互
UI Tests有什么用? 通过编写代码,记录和编码开发人员的操作过程,实现自动点击按钮和视图,自动输入文字的功能。
UI Tests的重要性在实际开发过程中,随着项目的壮大,功能也随之增加。 仅靠手工操作很难覆盖所有的测试用例。 特别是添加新功能后,旧功能也要重新测试一次。 结果表明,测试需要非常多的时间,需要进行回归测试。 这里产生了很多重复工作,但是这些重复工作中的一些可以自动进行。 此时UI Tests
使用方法的第一步:对于要添加ui测试的新项目,可以在创建项目时直接检查选项,如下图所示
对于现有项目,可以通过添加target来添加UI Tests,然后单击xcode菜单以找到target栏
在“测试”选项中,选择cocoa touch ui测试绑定
此时,test组件已成功添加,其在项目中的位置如下图所示
步骤2 :测试代码手动创建测试代码
打开测试文件,并在testExample (方法中添加测试代码
如果不知道如何编写测试代码,请参见自动生成的代码样式
自动生成测试步骤
选择测试文件后,单击录像按钮
此时开始操作,记录操作步骤,生成测试代码
下图是在一些操作之后自动生成的测试代码
此时,可以分析测试代码的语法,以便自己手动修改或手写测试代码
开始测试
单击testExample方法旁边的播放按钮开始自动测试。 此时,可以看到app正在自动操作
测试元素语法XCUIApplication:
继承XCUIElement。 这个类负责APP应用程序的生命周期,有两种主要方法
Launch(: )
启动程序
terminate(: )
结束程序
XCUIElement:
继承ns对象,实现协议XCUIElementAttributes,XCUIElementTypeQueryProvider
可以表示系统的各种UI元素
exist:
可以确定当前UI元素是否存在。 操作不存在的元素时,测试组件会抛出异常并中断测试
escendantsmatchingtype (type : xcuielementtype )-XCUIElementQuery:
取一类元素及其子类的集合
childrenmatchingtype (type : xcuielementtype )-XCUIElementQuery:
取某种元素的集合,不包含其子类
这两种方法的区别在于,如果只使用系统的UIButton,则可以使用childrenMatchingType;如果要查询自己定义的子Button,则使用descendantsMatchingType
此外,UI元素有多种交互式方法
tap ) ) :
按一下
doubleTap () :
双击
pressforduration (持续时间:时间间隔) :
长时间按,需要延迟操作时,这很有用
swipeUp () :
这个无法响应pan手势,暂时不知道在哪里可以使用。 可能是测试版的bug,但不说明
typetext (文本:字符串) :
在textField和textView中输入文本时使用。 请确认文本框中有输入焦点后再使用。 可以使用tap ()函数获取焦点
XCUIElementAttributes协议
包含ui访问权限的某些属性
如下图所示
可以很容易地看到现在要素的特征。 其中,identifier属性可用于直接读取元素,但该属性在UITextField中存在错误,暂时不知道原因
XCUIElementTypeQueryProvider协议
它包含系统中大多数UI控件的类型,可以通过读取属性来获取特定类型的UI集合
属性的部分屏幕快照如下
在创建Demo之前创建登录页面
单击login按钮进行登录认证,单击clear按钮清除文本
成功登录后可以进入个人信息页面
个人信息页面如下
单击modify按钮可以修改个人信息,单击Message按钮可以查找个人信息
看个人消息最后是消息界面
测试代码如下: func testLoginView() { let app = XCUIApplication() // 由于UITextField的id有问题,所以只能通过label的方式遍历元素来读取 let nameField = self.getFieldWithLbl("nameField") if self.canOperateElement(nameField) { nameField!.tap() nameField!.typeText("xiaoming") } let psdField = self.getFieldWithLbl("psdField") if self.canOperateElement(psdField) { psdField!.tap() psdField!.typeText("1234321") } // 通过UIButton的预设id来读取对应的按钮 let loginBtn = app.buttons["Login"] if self.canOperateElement(loginBtn) { loginBtn.tap() } // 开始一段延时,由于真实的登录是联网请求,所以不能直接获得结果,demo通过延时的方式来模拟联网请求 let window = app.windows.elementAtIndex(0) if self.canOperateElement(window) { // 延时3秒, 3秒后如果登录成功,则自动进入信息页面,如果登录失败,则弹出警告窗 window.pressForDuration(3) } // alert的id和labe都用不了,估计还是bug,所以只能通过数量判断 if app.alerts.count > 0 { // 登录失败 app.alerts.collectionViews.buttons["确定"].tap() let clear = app.buttons["Clear"] if self.canOperateElement(clear) { clear.tap() if self.canOperateElement(nameField) { nameField!.tap() nameField!.typeText("sun") } if self.canOperateElement(psdField) { psdField!.tap() psdField!.typeText("111111") } if self.canOperateElement(loginBtn) { loginBtn.tap() } if self.canOperateElement(window) { // 延时3秒, 3秒后如果登录成功,则自动进入信息页面,如果登录失败,则弹出警告窗 window.pressForDuration(3) } self.loginSuccess() } } else { // 登录成功 self.loginSuccess() } }
这里有几个需要特别注意的点:
1. rydyl的元素不存在时,它仍然可能返回一个元素对象,但这时候不能对其进行操作
2. rydyl要点击的元素被键盘或者UIAlertView遮挡时,执行tap方法会抛异常
详细实现可参照demo:
https://github.com/sunljz/demo/tree/master/iOS9/UITestDemo
测试代码如下: func testInfo() { let app = XCUIApplication() let window = app.windows.elementAtIndex(0) if self.canOperateElement(window) { // 延时2秒, 加载数据需要时间 window.pressForDuration(2) } let modifyBtn = app.buttons["modify"]; modifyBtn.tap() let sexSwitch = app.switches["sex"] sexSwitch.tap() let incrementButton = app.buttons["Increment"] incrementButton.tap() incrementButton.tap() incrementButton.tap() app.buttons["Decrement"].tap() let textView = app.textViews["feeling"] textView.tap() app.keys["Delete"].tap() app.keys["Delete"].tap() textView.typeText(" abc ") // 点击空白区域 let clearBtn = app.buttons["clearBtn"] clearBtn.tap() // 保存数据 modifyBtn.tap() window.pressForDuration(2) let messageBtn = app.buttons["message"] messageBtn.tap(); // 延时1秒, push view需要时间 window.pressForDuration(1) self.testMessage() }
这里需要特别注意以下两点:
1. textview获取焦点时无法选择焦点的位置
2. tap事件的触发位置是view的中心,所以当view的中心被遮挡时,要考虑使用其他view来代替
测试代码如下: func testMessage() { let app = XCUIApplication() let window = app.windows.elementAtIndex(0) if self.canOperateElement(window) { // 延时2秒, 加载数据需要时间 window.pressForDuration(2) } let table = app.tables table.childrenMatchingType(.Cell).elementAtIndex(8).tap() table.childrenMatchingType(.Cell).elementAtIndex(1).tap() }
这里需要注意一点:
1. 暂时无法获取到tableView的元素指针
总的来说,UI Tests只能用于一些基础功能的测试,验证app的功能是否可以正常使用,是否存在崩溃问题。但它也有很多不足之处,编写测试用例的过程非常繁琐,自动生成的代码几乎无法运行,功能单一,很多用例无法覆盖,而且bug很多,大大地限制了UI Tests在实际开发中的应用。希望正式版出来的时候能够修复这些问题,并开放更多的功能。
demo地址:
https://github.com/sunljz/demo/tree/master/iOS9/UITestDemo
欢迎各位提出改进建议,感谢!