UI Tests是什么?
UI Tests是一個(gè)自動(dòng)測(cè)試UI與交互的Testing組件
UI Tests有什么用稍算?
它可以通過編寫代碼、或者是記錄開發(fā)者的操作過程并代碼化,來實(shí)現(xiàn)自動(dòng)點(diǎn)擊某個(gè)按鈕域仇、視圖,或者自動(dòng)輸入文字等功能寺擂。
UI Tests的重要性
在實(shí)際的開發(fā)過程中暇务,隨著項(xiàng)目越做越大,功能越來越多沽讹,僅僅靠人工操作的方式來覆蓋所有測(cè)試用例是非常困難的般卑,尤其是加入新功能以后,舊的功能也要重新測(cè)試一遍爽雄,這導(dǎo)致了測(cè)試需要花非常多的時(shí)間來進(jìn)行回歸測(cè)試蝠检,這里產(chǎn)生了大量重復(fù)的工作,而這些重復(fù)的工作有些是可以自動(dòng)完成的挚瘟,這時(shí)候UI Tests就可以幫助解決這個(gè)問題了
使用方法
第一步:添加UI Tests
如果是新項(xiàng)目叹谁,則創(chuàng)建工程的時(shí)候可以直接勾選選項(xiàng),如下圖
如果是已有的項(xiàng)目乘盖,可以通過添加target的方式添加一個(gè)UI Tests焰檩,點(diǎn)擊xcode的菜單,找到target欄
在Test選項(xiàng)中選擇Cocoa Touch UI Testing Bundle
這時(shí)候test組件添加成功订框,它在項(xiàng)目中的位置如下圖所示
第二步:創(chuàng)建測(cè)試代碼
手動(dòng)創(chuàng)建測(cè)試代碼
打開測(cè)試文件析苫,在testExample()方法中添加測(cè)試代碼
如果不知道如何寫測(cè)試代碼,則可以參考自動(dòng)生成的代碼樣式
自動(dòng)生成測(cè)試步驟
選擇測(cè)試文件后,點(diǎn)擊錄制按鈕
這時(shí)候開始進(jìn)行操作衩侥,它會(huì)記錄你的操作步驟国旷,并生成測(cè)試代碼
下圖就是在一些操作后自動(dòng)生成的測(cè)試代碼
這時(shí)候可以分析測(cè)試代碼的語法,以便你自己手動(dòng)修改或者手寫測(cè)試代碼
開始測(cè)試
點(diǎn)擊testExample方法旁邊的播放按鈕茫死,它就開始進(jìn)行自動(dòng)測(cè)試了跪但,這時(shí)候你會(huì)看到app在自動(dòng)操作
下面介紹一下測(cè)試元素的語法
XCUIApplication:
繼承XCUIElement,這個(gè)類掌管應(yīng)用程序的生命周期峦萎,里面包含兩個(gè)主要方法
launch():
啟動(dòng)程序
terminate():
終止程序
**XCUIElement: **
繼承NSObject屡久,實(shí)現(xiàn)協(xié)議XCUIElementAttributes, XCUIElementTypeQueryProvider
可以表示系統(tǒng)的各種UI元素
exist:
可以讓你判斷當(dāng)前的UI元素是否存在,如果對(duì)一個(gè)不存在的元素進(jìn)行操作爱榔,會(huì)導(dǎo)致測(cè)試組件拋出異常并中斷測(cè)試
descendantsMatchingType(type:XCUIElementType)->XCUIElementQuery:
取某種類型的元素以及它的子類集合
childrenMatchingType(type:XCUIElementType)->XCUIElementQuery:
取某種類型的元素集合被环,不包含它的子類
這兩個(gè)方法的區(qū)別在于,你僅使用系統(tǒng)的UIButton時(shí)搓蚪,用childrenMatchingType就可以了蛤售,如果你還希望查詢自己定義的子Button,就要用descendantsMatchingType
另外UI元素還有一些交互方法
tap():
點(diǎn)擊
doubleTap():
雙擊
pressForDuration(duration: NSTimeInterval):
長按一段時(shí)間妒潭,在你需要進(jìn)行延時(shí)操作時(shí)悴能,這個(gè)就派上用場了
swipeUp():
這個(gè)響應(yīng)不了pan手勢(shì),暫時(shí)沒發(fā)現(xiàn)能用在什么地方雳灾,也可能是beta版的bug漠酿,先不解釋
typeText(text: String):
用于textField和textView輸入文本時(shí)使用,使用前要確保文本框獲得輸入焦點(diǎn)谎亩,可以使用tap()函數(shù)使其獲得焦點(diǎn)
XCUIElementAttributes協(xié)議
里面包含了UIAccessibility中的部分屬性
如下圖
可以方便你查看當(dāng)前元素的特征炒嘲,其中identifier屬性可用于直接讀取元素,不過該屬性在UITextField中有bug匈庭,暫時(shí)不清楚原因
XCUIElementTypeQueryProvider協(xié)議
里面包含了系統(tǒng)中大部分UI控件的類型夫凸,可通過讀屬性的方式取得某種類型的UI集合
部分屬性截圖如下
創(chuàng)建Demo
首先創(chuàng)建一個(gè)登錄頁面
點(diǎn)擊login按鈕進(jìn)行登錄驗(yàn)證,點(diǎn)擊clear按鈕會(huì)清除文本
登錄成功后可以去到個(gè)人信息頁面
個(gè)人信息頁面如下
點(diǎn)擊modify按鈕可以修改個(gè)人信息阱持,點(diǎn)擊Message按鈕可以查看個(gè)人消息
最后是消息界面
登錄頁面的測(cè)試
- 輸入一個(gè)錯(cuò)誤的賬號(hào)
- 驗(yàn)證結(jié)果
- 關(guān)閉警告窗
- 清除輸入記錄
- 輸入一個(gè)正確的賬號(hào)
- 驗(yàn)證結(jié)果
- 進(jìn)入個(gè)人信息頁面
測(cè)試代碼如下:
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的預(yù)設(shè)id來讀取對(duì)應(yīng)的按鈕
let loginBtn = app.buttons["Login"]
if self.canOperateElement(loginBtn) {
loginBtn.tap()
}
// 開始一段延時(shí),由于真實(shí)的登錄是聯(lián)網(wǎng)請(qǐng)求衷咽,所以不能直接獲得結(jié)果鸽扁,demo通過延時(shí)的方式來模擬聯(lián)網(wǎng)請(qǐng)求
let window = app.windows.elementAtIndex(0)
if self.canOperateElement(window) {
// 延時(shí)3秒, 3秒后如果登錄成功,則自動(dòng)進(jìn)入信息頁面镶骗,如果登錄失敗桶现,則彈出警告窗
window.pressForDuration(3)
}
// alert的id和labe都用不了,估計(jì)還是bug鼎姊,所以只能通過數(shù)量判斷
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) {
// 延時(shí)3秒, 3秒后如果登錄成功骡和,則自動(dòng)進(jìn)入信息頁面相赁,如果登錄失敗,則彈出警告窗
window.pressForDuration(3)
}
self.loginSuccess()
}
} else {
// 登錄成功
self.loginSuccess()
}
}
這里有幾個(gè)需要特別注意的點(diǎn):
- 當(dāng)你的元素不存在時(shí)即横,它仍然可能返回一個(gè)元素對(duì)象噪生,但這時(shí)候不能對(duì)其進(jìn)行操作
- 當(dāng)你要點(diǎn)擊的元素被鍵盤或者UIAlertView遮擋時(shí)裆赵,執(zhí)行tap方法會(huì)拋異常
詳細(xì)實(shí)現(xiàn)可參照demo:
https://github.com/sunljz/demo/tree/master/iOS9/UITestDemo
個(gè)人信息頁測(cè)試
- 修改性別
- 修改年齡
- 修改心情
- 保存修改
測(cè)試代碼如下:
func testInfo() {
let app = XCUIApplication()
let window = app.windows.elementAtIndex(0)
if self.canOperateElement(window) {
// 延時(shí)2秒, 加載數(shù)據(jù)需要時(shí)間
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 ")
// 點(diǎn)擊空白區(qū)域
let clearBtn = app.buttons["clearBtn"]
clearBtn.tap()
// 保存數(shù)據(jù)
modifyBtn.tap()
window.pressForDuration(2)
let messageBtn = app.buttons["message"]
messageBtn.tap();
// 延時(shí)1秒, push view需要時(shí)間
window.pressForDuration(1)
self.testMessage()
}
這里需要特別注意以下兩點(diǎn):
- textview獲取焦點(diǎn)時(shí)無法選擇焦點(diǎn)的位置
- tap事件的觸發(fā)位置是view的中心东囚,所以當(dāng)view的中心被遮擋時(shí),要考慮使用其他view來代替
個(gè)人消息界面測(cè)試
- 單元格的點(diǎn)擊
測(cè)試代碼如下:
func testMessage() {
let app = XCUIApplication()
let window = app.windows.elementAtIndex(0)
if self.canOperateElement(window) {
// 延時(shí)2秒, 加載數(shù)據(jù)需要時(shí)間
window.pressForDuration(2)
}
let table = app.tables
table.childrenMatchingType(.Cell).elementAtIndex(8).tap()
table.childrenMatchingType(.Cell).elementAtIndex(1).tap()
}
這里需要注意一點(diǎn):
- 暫時(shí)無法獲取到tableView的元素指針
總結(jié)
總的來說战授,UI Tests只能用于一些基礎(chǔ)功能的測(cè)試页藻,驗(yàn)證app的功能是否可以正常使用,是否存在崩潰問題植兰。但它也有很多不足之處份帐,編寫測(cè)試用例的過程非常繁瑣,自動(dòng)生成的代碼幾乎無法運(yùn)行楣导,功能單一废境,很多用例無法覆蓋,而且bug很多筒繁,大大地限制了UI Tests在實(shí)際開發(fā)中的應(yīng)用噩凹。希望正式版出來的時(shí)候能夠修復(fù)這些問題,并開放更多的功能毡咏。
demo地址:
https://github.com/sunljz/demo/tree/master/iOS9/UITestDemo
歡迎各位提出改進(jìn)建議驮宴,感謝!