×

现在开始把UI Testing用起来!

96
iHugo
2016.05.27 20:00* 字数 2083
自动化测试.jpg

一直很关注自动化测试,因为手动进行测试很枯燥,而且容易遗漏相关测试。苹果在Xcode7中引入了UI Testing,目前使用起来感觉良好。

UI测试并不是什么新鲜的东西。在过去我们一直在手动的进行UI测试,在运行中的App上进行操作并验证有没有问题。如果一个测试用例反复的进行手动测试是很枯燥的一件事情。所以就有人想出了利用计算机来做这些枯燥的工作。

UI自动化测试就是让计算机以用户角度自动化地进行测试,然后给出测试结果。

优秀的程序员可能会有编写单元测试的习惯。单元测试一般是由程序员针对某一块代码进行编写,用来验证代码逻辑是否正确。编写起来比较直接。

UI测试由于要以用户角度进行测试,所以编写起来不是那么直接,需要切换思考角度。

一些测试框架的对比。

框架 地址 语言 派系
TuneupJs https://github.com/vkolgi/tuneup_js JavaScript UI Automation
ynm3k https://github.com/douban/ynm3k JavaScript UI Automation
iOSDriver http://ios-driver.github.io/ios-driver/index.html Java UI Automation
Appium http://appium.io 多语言支持 UI Automation
KIF https://github.com/kif-framework/KIF Objective-C, Swift 私有api型
Frank http://www.testingwithfrank.com/index.html Cucumber 注入编译型
Calabash http://calaba.sh Cucumber, Ruby 注入编译型
XCTest https://developer.apple.com/xcode/features/cn/ Objective-C, Swift XCTest
EarlGrey https://github.com/google/EarlGrey Objective-C, Swift XCTest

UI Automation是Xcode7之前在iOS Instrument中的一个工具。可以编写javascript脚本供UI Automation调用来对UI进行测试。但是Xcode7之后这个就被UI Testing替代了。

上面表格中UI Automation系的UI测试框架,因为和Xcode整合的并不好,使用起来并不方便。而且需要使用javascrip去编写测试脚本,增加了学习成本。所以pass掉。

KIF由于使用了私有API,导致每次SDK升级KIF都可能需要大量修改代码,来适应新版本。个人觉得如果没有xctest这个框架那么它是比较好的选择。

Frank,Calabash这是由两个个著名提供敏捷咨询公司维护的,但是也要额外学习语言,增加了学习成本。并且代码没有开源,所以还是放弃吧。

还有谷歌的EarlGrey这个框架我还没有使用过,暂时不考虑。

最后我选择了XCTest框架。Apple 在 Xcode 7 中新加入了一套 UI Testing 的工具,其目的就是解决自动化UI测试这个问题。新的 UI Testing 比以往的解决方案要简单不少,特别是在创建测试用例的时候更集成了录制的功能,这有希望让 UI Testing 变得更为普及。
UI Testing和XCode整合的也比较好,能直接在Xcode中运行测试,也可以在命令行下进行测试,这就使得在持续集成中有了用武之地。同时相比使用Instruments中的UIAutomation调用 JavaScript 脚本与 app 交互,我们现在可以用 Swift 或者 Objective-C 直接在 Xcode 里编写和运行UI 测试。

为什么用UI自动化测试?

站在用户角度帮助我们理解需求

UI测试需要模拟用户操作对App进行测试。所以我们能借此来梳理一遍需求。

减少修改代码引入的bug

一直以来我们都是手动的进行UI测试。每次对代码有改动,我们就会对受影响的界面手动进行UI测试。但是我们往往会忽略其他相关UI的测试。

减少测试工作量,增加测试效率。

一遍一遍过UI测试用例是很枯燥的事情,如果能让计算机去做重复的事情能极大的解放我们的双手,增加测试效率。另外在持续集成过程中,我们能进行测试保证代码的正确性。

增加测试纬度

可能有的同学已经比较习惯编写单元测试了。我们常常会用单元测试来进行逻辑测试。增加了自动化UI测试,就会增加一个测试纬度,从用户角度对UI进行测试。

使用UI Testing时有一个附加的好处:测试辅助功能

之所以能使用UI Testing进行UI测试,有一个幕后英雄“Accessibility”。Accessibility这个是给残疾人士提供服务的一个功能。比如通过它盲人能听到机器朗读视图内容,从而可以像正常人一样使用app。其实我们的计算机也是比较“残疾”(智商不高)的,所以正好利用这个功能,可以读取到UI的内容。我们在使用UI Testing进行测试的同时也对App的辅助功能进行了测试,一举两得。:)

如何使用UI Testing?

Talk is cheap. Show me the code.

到了实战的环节了。我会先通过一个例子来简单的介绍下UI Testing。然后会对UI Tesing 的具体使用进行介绍。本来还有一个实战的,但是使用的公司项目就删掉了。我怕公司红线啊。:)

登陆Demo的UI自动化测试

这个demo的源码您能在这里下载到。你可以下载干净的源码然后跟着下面的内容一起来体验下UI Testing的魅力。如果你想直接看看效果请点击这里下载。

UI Tesing Demo.png

你可以点击这里观看视频演示。

Demo介绍

这个app很简单。只有一个用户名输入框、密码输入框和一个登录按钮。用户输入用户名和密码之后可以点击登录按钮进行登录。登录成功会弹出成功的提示。

demo.png

开始测试

这是我们编写的一个登录的测试用例。

用例名:登录
前置条件:无
后置条件:无
步骤:
1. 输入用户名abc
2. 输入密码123
3. 点击登录按钮
期待结果:弹出登录成功对话框

首先我们添加一个Target。


addtarget.png

然后增加一个函数叫loginSuccess。我们把鼠标光标放在函数体内。点击左下角的红色圆圈开始录制。这个时候我们可以正常操作,等操作完成之后。点击停止录制按钮结束录制。


录制测试过程.png

这个是录制好的代码。

func testLoginSuccess() {
    let app = XCUIApplication()
    app.textFields["name"].tap()
    app.textFields["name"]
    
    let passwordTextField = app.textFields["password"]
    passwordTextField.tap()
    passwordTextField.tap()
    app.textFields["password"]
    app.buttons["Login"].tap()
    
    let successAlert = app.alerts["success"]
    successAlert.staticTexts["success"].tap()
    successAlert.collectionViews.buttons["OK"].tap()
}

接下来我们修改下代码。

func testLoginSuccess() {
    let app = XCUIApplication()
        app.textFields["name"].tap()
        app.textFields["name"].typeText("abc")
        
        app.textFields["password"].tap()
        app.textFields["password"].typeText("123")
        
        app.buttons["Login"].tap()
        let successAlert =  app.alerts["success"]
        
        let exists = NSPredicate(format: "exists == true")
        expectationForPredicate(exists, evaluatedWithObject: successAlert, handler: nil)
        waitForExpectationsWithTimeout(5, handler: nil)
        XCTAssert(successAlert.exists)
}

改写完成之后,我们按住cmd+u就可以开始测试了。也可以在左侧的视图选中测试方法进行测试。


选择测试.png

测试完成之后我们能在这里看到测试结果。

测试结果.png

测试的一些日志能在这里找到。包括一些测试失败的原因还有截图信息。

测试日志.png

UI Testing

测试某个元素是否存在

XCTAssert(app.staticTexts["Welcome"].exists)

等待元素出现

let goLabel = self.app.staticTexts["Go!"]
XCTAssertFalse(goLabel.exists)

let exists = NSPredicate(format: "exists == true")
expectationForPredicate(exists, evaluatedWithObject: goLabel, handler: nil)

app.buttons["Ready, set..."].tap()
waitForExpectationsWithTimeout(5, handler: nil)
XCTAssert(goLabel.exists)

点击按钮

app.buttons["Add"].tap()

输入文字

let textField = app.textFields["Username"]
textField.tap()
textField.typeText("joemasilotti")

取消警告提示框

app.alerts["Alert Title"].buttons["Button Title"].tap()

处理系统的提示框

addUIInterruptionMonitorWithDescription("Location Services") { (alert) -> Bool in
  alert.buttons["Allow"].tap()
  return true
}

app.buttons["Request Location"].tap()
app.tap() // need to interact with the app again for the handler to fire

UISlider

app.sliders.element.adjustToNormalizedSliderPosition(0.7)

UIPickerView

app.pickerWheels.element.adjustToPickerWheelValue("Picker Wheel Item Title")

有多个section的UIPickerView操作。

let firstPredicate = NSPredicate(format: "label BEGINSWITH 'First Picker'")
let firstPicker = app.pickerWheels.elementMatchingPredicate(firstPredicate)
firstPicker.adjustToPickerWheelValue("first value")

let secondPredicate = NSPredicate(format: "label BEGINSWITH 'Second Picker'")
let secondPicker = app.pickerWheels.elementMatchingPredicate(secondPredicate)
secondPicker.adjustToPickerWheelValue("second value")

点击webview的超链接

app.links["Tweet this"].tap()

验证视图控制题的标题

XCTAssert(app.navigationBars["Details"].exists)

移动Tabview Cell

let topButton = app.buttons["Reorder Top Cell"]
let bottomButton = app.buttons["Reorder Bottom Cell"]
bottomButton.pressForDuration(0.5, thenDragToElement: topButton)

XCTAssertLessThanOrEqual(bottomButton.frame.maxY, topButton.frame.minY)

下拉刷新

let firstCell = app.staticTexts["Adrienne"]
let start = firstCell.coordinateWithNormalizedOffset(CGVectorMake(0, 0))
let finish = firstCell.coordinateWithNormalizedOffset(CGVectorMake(0, 6))
start.pressForDuration(0, thenDragToCoordinate: finish)

Push & Pop 视图控制器

app.buttons["More Info"].tap()
XCTAssert(app.navigationBars["Volleyball?"].exists)

总结

测试工作时枯燥的,但是必不可少的。所以才会有自动测试这个概念出现。利用事先写好的测试用例能让计算机帮助我们进行自动测试是一件很令人开心的事情。

我们通过UI Testing进行UI测试的步骤一般是这样的。

  1. 编写UI测试用例
  2. 打开录制功能,将测试流程录制并生成相关代码。
  3. 将生成的代码修改的更灵活,稳定。

编写好测试代码之后,不管是我们修改了代码还是在持续集成过程中都可以对UI进行自动化的测试。大量的节省了人力,也减少了人疏忽忘记测试最后bug没有查出来的问题。最后我想说的凡事都有个度。比如某一个功能需求一直在变动,这个时候就不要编写这个功能涉及UI测试了。要不你总有一天会感觉一直在疲于奔命,会放弃编写测试代码。:)

iOS & Mac osx的开发
Web note ad 1