大約兩星期前,蘋果釋出 Xcode 7 的第一個測試版,附帶閃亮新功能,如內建的 UI 測試功能。除了 Swift,這是最容易讓我興奮的功能。我迫不及待地嘗試了一番。UI 測試並不是什麼新技術,早在 2011 年就已經有 KIF。這是一個偉大的工具,但也有其侷限性:速度慢,不是 100% 可靠的,而且它依賴於私有 API。因此,官方的解決方案似乎是個不錯的主意。
該解決方案整合到蘋果的單元測試框架 XCTest 中。它的使用與 KIF 很相似。然而,它是建立在新版本的 UIAutomation 上,更穩定可靠。因為它內建在 Xcode 中,具有更強大的功能,如 UI 記錄和自動截圖,讓它變得更加方便,更加有用。
在下面的章節,我會談論兩個框架之間最大的區別。
靈活性
那麼,我說幹就幹,開始為 ImagePickerSheetController 重寫 UI 測試程式碼,我的開源專案之一。它本質上是在 iOS iMessage 選擇圖片的自定義操作表(action sheet)。
我第一個想重寫的是一個非常基本的測試。簡單地測試下當點選取消按鈕時,控制器是否會撤回。
1 2 3 4 5 6 7 |
func testDismissal() { let imageController = ImagePickerSheetController() imageController.addAction(ImageAction(title: “Cancel”)) rootViewController.presentViewController(imageController, animated: true, completion: nil) tester().tapViewWithAccessibilityLabel(“Cancel”) tester().waitForAbsenceOfViewWithAccessibilityIdentifier(ID) } |
雖然這看起來非常簡單,但這使用 XCTest 不可能實現。問題是,Xcode 的 UI 測試不允許訪問實際的應用。API 提供的只是充當個代理。試圖直接檢索一個檢視而不是使用 XCUIElement,這是行不通的,XCUIElement 只是用來展示檢視。
在 WWDC 開發工具實驗室有人告訴我,XCTest 的主要目標是讓應用更容易測試。使用這個框架還是很明智的,於是我開始重寫我的一個叫 Crimson 的應用測試。
Crimson 鍵盤顯示了字母“E”的不同聲調。
經過幾個基本測試後,我開始對“聲調檢視”進行操作。它顯示了一個特定字母的不同聲調。我想測試的是下面兩點:
- 當檢視顯示時,第一個字母,在這裡是“e”,應被選中
- 聲調檢視的顏色應與鍵一致
我初以為不可能寫這測試。和之前一樣,你不能只通過審查實際的檢視來檢查屬性。在我仔細查閱文件一兩分鐘後,我偶然發現 XCUIElement 的實用屬性 value:
1 2 |
/*! The raw value attribute of the element. Depending on the element, the actual type can vary. */ var value: AnyObject? { get } |
它所做的就是返回底層 UI 元素的 accessibilityValue。這讓測試訪問屬性成為可能。所以,我需要唯一做的就是設定聲調檢視的 accessibilityValue:
1 2 3 4 5 6 7 |
class AccentView: UIView { var selectedLetter: String { didSet { accessibilityValue = selectedLetter } } } |
直到我想檢測顏色,這一切都進展順利。由於 accessibilityValue 是字串型別而不是字典,它不可能再訪問第二個屬性。除此之外,accessibilityValue 就像是使用者面對的 accessibilityIdentifier 一樣。把 accessibilityValue 設定成模糊值,如一種顏色,是種非常不好的做法,而且完全違背了目的。
因此本次測試也是不可能實現的。雖然很容易檢查元素的 frame 和 label 值,但它幾乎不可能用來測試其他東西。
效能
為了比較兩種框架的效能,我為示例專案寫了 3 份測試。為了模擬一個合理的測試組,每個測試都執行 20 遍,使得總共有 60 個測試。
對於第一次測量,我實現了文件中的測試。對於 XCTest,這意味著我會在每次測試後重新執行該應用。KIF 不支援這樣,這讓它在第一個比較中顯得比較快速。
第二次測量通過返回導航而不是重新執行,重置了目標應用的狀態。
第三次測量,我又取消了所有的動畫。
因為 Xcode 7 不斷崩潰,我不得不將迭代的次數減少到 5,然後將結果乘以 4。
正如我們在第一次比較中看到的,為每個規格重新執行應用,使得該測試組極其緩慢。令人驚訝的是 UI 測試模版建議使用這種重置方式,而不是通過導航,因為在大多情況下,沒必要使用這種方式。
第二次比較顯示,在正常情況下,XCTest 比 KIF 稍微快一點。需要注意的是,在一個比較大的且每天需要多次評估的測試組中,10 秒就可以省下大量的時間。
最後的比較中,XCTest 讓人眼前一亮。然而,並不是每個專案都可以禁用所有動畫。試想一個帶許多自定義過渡效果的專案,測試組還是應確認過渡的正常執行。
對此的解決方案就是,把測試組分成不同執行環境的類,以控制應用的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class UITests: XCTestCase { private var launched = false let app = XCUIApplication() override func setUp() { super.setUp() continueAfterFailure = false launchIfNecessary() } private func launchIfNecessary() { if !launched { launched = true app.launchEnvironment = [“animations”: “0”] app.launch() } } func testDetail() { ... } func testPopover() { ... } func testActionSheet() { ... } } |
然後,AppDelegate 根據執行環境進行配置。
1 2 3 4 5 6 7 8 9 10 |
class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { if NSProcessInfo.processInfo().environment[“animations”] == “0” { UIView.setAnimationsEnabled(false) } ... return true } } |
這可以大大減少你等待測試通過的時間。
總結
XCTest 可以讓我們很容易編寫簡單的 UI 測試。而想要更高階的測試則是很困難甚至是不可能的。而 KIF 則更為靈活些。
然而,相比 KIF,XCTest 在查詢 UI 元素上稍快些。在禁用動畫的情況下,XCTest 是非常快。
雖然我希望 Xcode 內建的 UI 測試可以幫助我重寫所有專案測試,但是我放棄了。我根本無法找到可以編寫所有測試的方式。
乾貨
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式