在上線之后發(fā)生了幾次崩潰閃退, 需要緊急修復(fù)的情況之后, 我決定我要?jiǎng)邮至?..
分析了這幾次情況之后, 發(fā)現(xiàn)其實(shí)大的邏輯都沒有錯(cuò), 但是細(xì)的東西特別容易出簍子, 例如說布爾條件寫反了, 某個(gè) @IBOutlet
的控件改名了, 刪掉了, 忘了去 storyboard 里處理掉它, 就會(huì)發(fā)生 setValue: forUndefinedKey:
的錯(cuò)誤, 本來我是想直接 swizzle 掉這個(gè)方法, 不讓它拋出錯(cuò)誤, 但是想想又覺得不值得. 難道終于要開始學(xué)一下怎么寫測試了嗎?
然后突然想起了之前好像看到過一個(gè) UI 測試的框架, 可以自動(dòng)幫忙測試 UI, 找到之后就開始用, 然后一發(fā)不可收拾.
倉庫的位置在這里 GitHub - zalando/SwiftMonkey: A framework for doing randomised UI testing of iOS apps
簡介
這個(gè)庫讓我想起了無限猴子理論, 其實(shí)也類似, 就是產(chǎn)生間隔一段事件就產(chǎn)生一個(gè)隨機(jī)操作事件, 例如點(diǎn)擊拖拽, 閃退的話是最容易發(fā)現(xiàn)的, 或者是你看到一些錯(cuò)誤的數(shù)據(jù)和 UI 呈現(xiàn).(效果圖是張 gif, 手機(jī)端加載可能會(huì)比較慢)
這個(gè)庫分成兩部分:
- 主體是 SwiftMonkey, 依賴于 XCUITest, 調(diào)用了一些私有方法去發(fā)起操作事件
- SwiftMonkeyPaws, 負(fù)責(zé)呈現(xiàn)操作事件的視覺效果, 上面的動(dòng)圖里, 那些小手掌就是 SwiftMonkeyPaws 制造出來的, 需要直接接入到 app 里面
接入流程
官方文檔目前還不是很詳細(xì), 我花了一點(diǎn)時(shí)間才把這個(gè)庫給搞明白, 所以大概介紹一下接入流程.
包管理, 很簡單嘛, 支持 Carthage 和 Cocoapods 兩種方式, 想用哪個(gè)用哪個(gè).
但使用 Cocoapods 的同學(xué)有一點(diǎn)事情要注意, 作者忘了 push podspec 到主倉庫了, 所以我們 pod 里搜索和安裝的都是 1.0.0 版本, 最低支持 iOS 9.0, 而最新的 1.0.1 版本最低支持 8.0.
解決方法也很簡單, pod 的時(shí)候指定倉庫就行了, 就像這樣:
pod 'SwiftMonkey', :git => 'https://github.com/zalando/SwiftMonkey.git'
安裝完之后, 在 AppDelegate 里面我們需要初始化一下 SwiftMonkeyPaws, 有視覺效果畢竟會(huì)更好一點(diǎn)
import SwiftMonkeyPaws
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var paws: MonkeyPaws?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
#if DEBUG
if CommandLine.arguments.contains("--MonkeyPaws") {
paws = MonkeyPaws(view: window!)
}
#endif
return true
}
}
記得要在 AppDelegate 里聲明一個(gè) paws 去維持引用計(jì)數(shù), 然后 MonkeyPaws 就會(huì) swizzle 掉 UITouch 的方法, 讓每次點(diǎn)擊, 拖拽都會(huì)有相應(yīng)的視覺效果.
這里我們看到一個(gè) CommandLine.argments.contains(“—MonkeyPaws”)
可能會(huì)比較奇怪, 這段代碼是為了區(qū)分開 app 是否跑在測試模式下的, 然后為了不在正式版里加入這段代碼, 我們還加上了 compile flag 去判斷是否編譯這段代碼. 直接加一個(gè) ConfiguationSet 也行, 但不優(yōu)雅, 也沒必要...
接下里我們就去處理 UI 測試的代碼:
import SwiftMonkey
class UITest: XCTestCase {
override func setUp() {
super.setUp()
continueAfterFailure = false
let app = XCUIApplication()
app.launchArguments.append("--MonkeyPaws")
app.launch()
}
}
在 setup 方法里, 需要注意的就是最好把 continueAfterFailure
設(shè)為 false, 讓代碼出錯(cuò)時(shí)能夠停留在出錯(cuò)的位置那里, 方便我們 DEBUG, 畢竟我們使用的不是常規(guī)的測試方法, 測試用例跟代碼之間沒有一一對(duì)應(yīng)的關(guān)系.
還有一個(gè)就是加上參數(shù) —-MonkeyPaws
去區(qū)分運(yùn)行和測試狀態(tài), 不加的話 paws 就不會(huì)運(yùn)行了.
那么久該開始寫用例了, 我用的方式比較粗暴.
func testMonkey() {
let application = XCUIApplication()
// Workaround for bug in Xcode 7.3. Snapshots are not properly updated
// when you initially call app.frame, resulting in a zero-sized rect.
// Doing a random query seems to update everything properly.
// TODO: Remove this when the Xcode bug is fixed!
_ = application.descendants(matching: .any).element(boundBy: 0).frame
let monkey = Monkey(frame: application.frame)
monkey.addXCTestTapAction(weight: 25)
monkey.addXCTestDragAction(weight: 200)
monkey.addXCTestTapAction(weight: 100)
monkey.addXCTestDragAction(weight: 30)
monkey.monkeyAround(iterations: 360000)
}
前面的代碼是我照抄官方給的例子的, 不加的話會(huì)有 bug.
接著我們初始化一只 Monkey, 然后給它添加一些動(dòng)作, 其實(shí)還有什么各種 pinch, peek, pop 之類的, 但我的項(xiàng)目比較簡單, 所以我就只加了點(diǎn)擊和拖拽動(dòng)作, weight 是間隔. monkeyAround
就是開始隨機(jī)操作, iteration 是操作的次數(shù), 操作滿 360000 次就會(huì)停止.
這基本上就是我的用法, 里面還有一些挺有趣的東西, 之后有空的話我還會(huì)再探索一下, 例如添加多幾個(gè)用例, 然后先跳轉(zhuǎn)到新寫的 ViewController 那里, 讓這只猴子把里面的東西全都搞亂, 看看有啥 bug.
使用體驗(yàn)
到目前位置我用了這個(gè)庫兩三天, 每天中午去吃飯都會(huì)跑一下, 發(fā)現(xiàn)了幾個(gè) bug, 三個(gè)是低級(jí)錯(cuò)誤, 兩個(gè)比較隱晦, 主要是關(guān)于多次點(diǎn)擊重復(fù)觸發(fā)關(guān)鍵事件, 例如說一秒內(nèi)連續(xù)點(diǎn)了七八次提交訂單, 導(dǎo)致發(fā)出去七八個(gè)請(qǐng)求, 實(shí)際在網(wǎng)絡(luò)情況不好的時(shí)候, 用戶也有可能心急多次點(diǎn)擊, 所以挺好的, 幫我提前預(yù)防了一些問題.
其實(shí)覺得無論是哪種情況, 都挺適合用一下這個(gè)庫去找到一些低級(jí)的明顯的 bug, 強(qiáng)烈推薦大家用一下.
覺得我寫的還不錯(cuò)的話可以關(guān)注一下我的博客