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()
}
}
當然以上只是一些簡單的思路和測試修改,我們可以根據項目需要進行優化改進。
