一直很關注自動化測試,因為手動進行測試很枯燥,而且容易遺漏相關測試。蘋果在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中的一個工具??梢跃帉?a href='http://www.kjueaiud.com/ceshi/ruanjianceshikaifajishu/rjcskf' target='_blank'>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測試需要模擬用戶操作對App進行測試。所以我們能借此來梳理一遍需求。
減少修改代碼引入的bug
一直以來我們都是手動的進行UI測試。每次對代碼有改動,我們就會對受影響的界面手動進行UI測試。但是我們往往會忽略其他相關UI的測試。
減少測試工作量,增加測試效率。
一遍一遍過UI測試用例是很枯燥的事情,如果能讓計算機去做重復的事情能極大的解放我們的雙手,增加測試效率。另外在持續集成過程中,我們能進行測試保證代碼的正確性。
增加測試緯度
可能有的同學已經比較習慣編寫單元測試了。我們常常會用單元測試來進行邏輯測試。增加了自動化UI測試,就會增加一個測試緯度,從用戶角度對UI進行測試。
使用UI Testing時有一個附加的好處:測試輔助功能
之所以能使用UI Testing進行UI測試,有一個幕后英雄“Accessibility”。Accessibility這個是給殘疾人士提供服務的一個功能。比如通過它盲人能聽到機器朗讀視圖內容,從而可以像正常人一樣使用app。其實我們的計算機也是比較“殘疾”(智商不高)的,所以正好利用這個功能,可以讀取到UI的內容。我們在使用UI Testing進行測試的同時也對App的輔助功能進行了測試,一舉兩得。:)
Talk is cheap. Show me the code.
到了實戰的環節了。我會先通過一個例子來簡單的介紹下UI Testing。然后會對UI Tesing 的具體使用進行介紹。本來還有一個實戰的,但是使用的公司項目就刪掉了。我怕公司紅線啊。:)
這個demo的源碼您能在這里下載到。你可以下載干凈的源碼然后跟著下面的內容一起來體驗下UI Testing的魅力。如果你想直接看看效果請點擊這里下載。
這個app很簡單。只有一個用戶名輸入框、密碼輸入框和一個登錄按鈕。用戶輸入用戶名和密碼之后可以點擊登錄按鈕進行登錄。登錄成功會彈出成功的提示。
這是我們編寫的一個登錄的測試用例。
用例名:登錄
前置條件:無
后置條件:無
步驟:
1. 輸入用戶名abc
2. 輸入密碼123
3. 點擊登錄按鈕
期待結果:彈出登錄成功對話框
首先我們添加一個Target。
然后增加一個函數叫loginSuccess。我們把鼠標光標放在函數體內。點擊左下角的紅色圓圈開始錄制。這個時候我們可以正常操作,等操作完成之后。點擊停止錄制按鈕結束錄制。
這個是錄制好的代碼。
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就可以開始測試了。也可以在左側的視圖選中測試方法進行測試。
測試完成之后我們能在這里看到測試結果。
測試的一些日志能在這里找到。包括一些測試失敗的原因還有截圖信息。
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
app.sliders.element.adjustToNormalizedSliderPosition(0.7)
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")
app.links["Tweet this"].tap()
XCTAssert(app.navigationBars["Details"].exists)
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)
app.buttons["More Info"].tap()
XCTAssert(app.navigationBars["Volleyball?"].exists)
測試工作時枯燥的,但是必不可少的。所以才會有自動測試這個概念出現。利用事先寫好的測試用例能讓計算機幫助我們進行自動測試是一件很令人開心的事情。
我們通過UI Testing進行UI測試的步驟一般是這樣的。
編寫好測試代碼之后,不管是我們修改了代碼還是在持續集成過程中都可以對UI進行自動化的測試。大量的節省了人力,也減少了人疏忽忘記測試最后bug沒有查出來的問題。最后我想說的凡事都有個度。比如某一個功能需求一直在變動,這個時候就不要編寫這個功能涉及UI測試了。要不你總有一天會感覺一直在疲于奔命,會放棄編寫測試代碼。:)
原文轉自:http://www.jianshu.com/p/31367c97c67d