UI Tests是什麼?
UI Tests是一個自動化的測試UI與互動的Testing組件
UI Tests有什麼用?
它可以通過編寫代碼、或者是記錄開發人員的操作過程並代碼化,來實現自動點擊某個按鈕、視圖,或者自動輸入文字等功能。
UI Tests的重要性
在實際的開發過程中,隨著項目越做越大,功能越來越多,僅僅靠人工操作的方式來覆蓋所有測試案例是非常困難的,尤其是加入新功能以後,舊的功能也要重新測試一遍,這導致了測試需要花非常多的時間來進行迴歸測試,這裡產生了大量重複的工作,而這些重複的工作有些是可以自動完成的,這時候UI Tests就可以協助解決這個問題了。
使用方法
第一步:添加UI Tests
如果是新項目,則建立工程的時候可以直接勾選選項,如下圖
如果是已有的項目,可以通過添加target的方式添加一個UI Tests,點擊xcode的菜單,找到target欄
在Test選項中選擇Cocoa Touch UI Testing Bundle
這時候test組件添加成功,它在項目中的位置如下圖所示
第二步:建立測試代碼
手動建立測試代碼
開啟測試檔案,在testExample()方法中添加測試代碼
如果不知道如何寫測試代碼,則可以參考自動產生的程式碼樣式
自動產生測試步驟
選擇測試檔案後,點擊錄製按鈕
這時候開始進行操作,它會記錄你的操作步驟,並產生測試代碼
下圖就是在一些操作後自動產生的測試代碼
這時候可以分析測試代碼的文法,以便你自己手動修改或者手寫測試代碼
開始測試
點擊testExample方法旁邊的播放按鈕,它就開始進行自動化的測試了,這時候你會看到app在自動操作
下面介紹一下測試元素的文法
XCUIApplication:
繼承XCUIElement,這個類掌管應用程式的生命週期,裡麵包含兩個主要方法
launch():
啟動程式
terminate():
終止程式
XCUIElement:
繼承NSObject,實現協議XCUIElementAttributes, XCUIElementTypeQueryProvider
可以表示系統的各種UI元素
exist:
可以讓你判斷當前的UI元素是否存在,如果對一個不存在的元素進行操作,會導致測試組件拋出異常並中斷測試
descendantsMatchingType(type:XCUIElementType)->XCUIElementQuery:
取某種類型的元素以及它的子類集合
childrenMatchingType(type:XCUIElementType)->XCUIElementQuery:
取某種類型的元素集合,不包含它的子類
這兩個方法的區別在於,你僅使用系統的UIButton時,用childrenMatchingType就可以了,如果你還希望查詢自己定義的子Button,就要用descendantsMatchingType
另外UI元素還有一些互動方法
tap(): 點擊
doubleTap(): 雙擊
pressForDuration(duration: NSTimeInterval): 長按一段時間,在你需要進行延時操作時,這個就派上用場了
swipeUp(): 這個響應不了pan手勢,暫時沒發現能用在什麼地方,也可能是beta版的bug,先不解釋
typeText(text: String): 用於textField和textView輸入文本時使用,使用前要確保文字框獲得輸入焦點,可以使用tap()函數使其獲得焦點
XCUIElementAttributes協議
裡麵包含了UIAccessibility中的部分屬性
如下圖
可以方便你查看當前元素的特徵,其中identifier屬性可用於直接讀取元素,不過該屬性在UITextField中有bug,暫時不清楚原因
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. 當你的元素不存在時,它仍然可能返回一個元素對象,但這時候不能對其進行操作
2. 當你要點擊的元素被鍵盤或者UIAlertView遮擋時,執行tap方法會拋異常
詳細實現可參照demo: https://github.com/sunGd/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在實際開發中的應用。希望正式版出來的時候能夠修複這些問題,並開放更多的功能。