swift monkey是用來在iOS端進行monkey測試的,用swift語言編寫,基於XCTest測試框架,調用私有api XCEventGenerator,不斷生成event事件,不過在Xcode10.1以上XCTestFramework已經去掉了這個API,所以如果是想在10.1以上使用的話需要進行二次開發。
在使用Android端的monkey的時候就發現不同的app對monkey測試的需求是不同的,基本都需要對原生的工具框架進行二次開發來滿足不同的測試需求,Android的話fastmonkey基本可以滿足一些定制化場景了,但是iOS這邊還不夠,因此我們查看下swiftmonkey源碼,根據自己需要進行二次開發。
具體的使用步驟就不多贅述了,網上的資源也很多,就記錄一個github地址吧
其實發現這個工具也有段時間沒有更新了。
框架構成
簡單介紹下整個工具的文件構成。
Monkey: 是程序入口,主要是monkey構造,monkey運行等
MonkeyXCTest: 看注釋的話本來是要擴展monkey使用公共的XCTest API來生成事件的,但是沒寫。。。
MonkeyXCTestPrivate:這塊才是利用私有API生成各種事件的代碼
MonkeyUIAutomation: 這塊是利用UIautomation框架來執行各種事件的,但是只支持模擬器
Random: 這塊是生成各種隨機數的函數
主要部分源碼分析
monkeyAround執行方法(按次數執行,按時間執行),通過循環生成隨機事件
publicfuncmonkeyAround(iterations: Int) { for_in1... iterations { actRandomly() actRegularly() } }
actRandomly( ) 是將添加的隨機事件執行
actRegular( ) 是固定間隔執行事件
/// Generate one random event. publicfuncactRandomly() { letx = r.randomDouble() * totalWeight foraction inrandomActions{ ifx < action.accumulatedWeight { action.action() return } } } /// Generate any pending fixed-interval events. publicfuncactRegularly() { actionCounter+= 1 foraction inregularActions{ ifactionCounter% action.interval == 0{ action.action() } } }
可以看到是從random隨機事件數組中取出action然后執行,在添加事件的時候需要添加事件所占比例,在事件執行的時候也會根據事件比例去執行。
那么事件是從哪里隨機生成的呢?
在使用monkey的時候,需要添加隨機事件。
例如:
monkey.addDefaultXCTestPrivateActions()
添加默認XCTest私有事件,查看詳細方法,可以看到添加的隨機事件的比例
publicfuncaddDefaultXCTestPrivateActions() {
addXCTestTapAction(weight: 25)
addXCTestLongPressAction(weight: 1)
addXCTestDragAction(weight: 0)
addXCTestPinchCloseAction(weight: 0)
addXCTestPinchOpenAction(weight: 0)
addXCTestRotateAction(weight: 0)
//addXCTestOrientationAction(weight: 1) // TODO: Investigate why this does not work.
}
可以看到它默認給我們添加了幾個event,並且設置了權重
那來看下第一個event,tapAction是怎么添加的
在addXCTestTapAction方法里,添加了一個閉包函數,生成了隨機的point,然后調用XCEventGenerator來執行,函數比較長就不粘貼了。
值得注意的一點是,addXCTestTapAction中是調用了addAction方法來添加事件到隨機數組,然后在執行時遍歷執行
在addAction方法中還有一個點是里面又嵌套了個閉包函數用來監聽當前application始終是我們要測試的app,如果發現因為調用一些系統手勢或事件導致退出app,會重新拉回。
funcactInForeground(_action: @escapingActionClosure) -> ActionClosure{ return{ guard#available(iOS9.0, *) else{ action() return } letclosure: ActionClosure= { // state來判斷當前app執行狀態 ifXCUIApplication().state!= .runningForeground{ XCUIApplication().activate() } action() } ifThread.isMainThread{ closure() } else{ DispatchQueue.main.async(execute: closure) } } }
至此我們可以理出swiftmonkey的執行過程
1.初始化monkey
2.添加隨機事件,設置權重
3.執行monkey
二次開發思路
如何二次開發?
以解決swiftmonkey插樁到app代碼的問題為例。
常規的使用方法是將monkey添加到我們自己的項目中才能執行,但是當我們理解了它的原理就可以稍微改造下。
swiftmonkey是基於xcuitest來執行的,因此首先需要在項目中由xcuiapplication吊起測試的app,然后隨機執行。但是如果了解XCTest 的話就知道XCTest是支持吊起其它app的,只要傳入app的bundleIdentifier就可以,因此我們可以隨便建一個Xcode項目,然后導入swiftmonkey文件,創建uitest文件,但是啟動monkey前指定我們需要測試app的bundleid就可以了。
例如:
letapp2 = XCUIApplication(bundleIdentifier: "com.myapp.app")
app2.launch()
但是執行后發現還是會拉回到創建的這個假app,為什么呢,分析源碼的時候說過一個點,在每次執行事件的時候都會判斷一下當前app(monkey所在項目)是否啟動活躍在前台,如果不是就會拉起,那就好了我們把判斷的application改成自己實際要測試的app就可以了
例如:
在actInForeground方法中,把application改成實際要測試的
原來是:
if XCUIApplication().state!= .runningForeground{ XCUIApplication().activate() }
改成:
if XCUIApplication(bundleIdentifier: "com.myapp.app").state!= .runningForeground{ XCUIApplication(bundleIdentifier: "com.myapp.app").activate() }
這樣每次就會判斷實際要測試的app是否在前台運行,如果不是會自動拉起。
經過上面的改造swiftmonkey就不需要插樁了。
替換私有API,使之支持Xcode10.1以上
我做了個測試,如果使用公有API確實速度慢了很多。
以執行50次tapAction為例
使用公共API的速度:14秒左右,大約每秒3-4個action
使用私有API的速度:5秒左右,大約每秒10個action
速度相比還是差距比較大的,但是個人覺得如果是app測試而不是手機測試,沒必要過分追求多大的壓力測試,每秒3-4個action的已經超出用戶常規的app操作頻率了
通過修改addXCTestTapAction方法
原來是:
let semaphore = DispatchSemaphore(value: 0) // self!.sharedXCEventGenerator.tapAtTouchLocations(locations, numberOfTaps: numberOfTaps, orientation: orientationValue) { // semaphore.signal() // } // semaphore.wait()
改成:
if #available(iOS9.0, *) { letapp = XCUIApplication() letcoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: locations[0].x/(app.frame.maxX/2), dy: locations[0].y/(app.frame.maxY/2))) coordinate.tap() } else{ // Fallback on earlier versions }
如何插入業務邏輯代碼
例如判斷登錄,然后如果沒有登錄就先執行登錄操作
有幾種解決方案:
1. 每次執行event前判斷是否增加業務功能代碼,直接執行功能代碼
2. 插入定時循環事件來執行功能代碼,swiftmonkey有兩種執行事件的方式,
actRandomly( ) 是將添加的隨機事件執行
actRegular( ) 是固定間隔執行事件,可以在這里面增加事件,特定的功能邏輯事件
以第一種為例:
func actInForeground(_ action: @escaping ActionClosure) -> ActionClosure { return { guard #available(iOS 9.0, *) else { action() return } let closure: ActionClosure = { // if XCUIApplication(bundleIdentifier: "com.sanjieke.app").state != .runningForeground { // XCUIApplication(bundleIdentifier: "com.sanjieke.app").activate() // } if self.currentApp.state != .runningForeground { self.currentApp.activate() } //判斷是否需要登錄 if self.currentApp.buttons["密碼登錄"].exists{ self.login(app: self.currentApp, user: "15122223333", password: "654321") } action() } if Thread.isMainThread { closure() } else { DispatchQueue.main.async(execute: closure) } } }
func login(app: XCUIApplication, user: String, password: String) { if app.buttons["密碼登錄"].exists { let button = app.buttons["密碼登錄"] button.tap() let textField = app.textFields["輸入手機號"] textField.tap() textField.typeText(user) let secureTextField = app.secureTextFields["輸入密碼"] secureTextField.tap() secureTextField.typeText(password) let login = app.buttons["登錄按鈕"] login.tap() } }
當然以上只是一些簡單的思路和測試修改,我們可以根據項目需要進行優化改進。