swiftmonkey 源码剖析及二次开发思路

匿名 (未验证) 提交于 2019-12-02 23:52:01
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()         }     }

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() {
}
可以看到它默认给我们添加了几个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就可以了。
例如:
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就不需要插桩了。
再比如Xcode10.1以上不支持XCEventGenerator,那我们是不是可以换掉这个API,之前查过资料也确实没有更好的API能用,如果不换掉只能使用10.1以前的XCTestFramework,但是随之XCTest更新,最好还是能向上发展而不是向下兼容。
因此我做了个测试,如果使用公有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             }   

当然以上只是一个简单的思路和测试修改,我们可以根据项目需要进行优化改进。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!