swift monkey是用来在iOS端进行monkey测试的,用swift语言编写,基于XCTest测试框架,调用私有api XCEventGenerator,不断生成event事件,不过在Xcode10.1以上XCTestFramework已经去掉了这个API,所以如果是想在10.1以上使用的话需要进行二次开发。
在使用Android端的monkey的时候就发现不同的app对monkey测试的需求是不同的,基本都需要对原生的工具框架进行二次开发来满足不同的测试需求,Android的话fastmonkey基本可以满足一些定制化场景了,但是iOS这边还不够,因此我们查看下swiftmonkey源码,根据自己需要进行二次开发。
具体的使用步骤就不多赘述了,网上的资源也很多,就记录一个github地址吧
https://github.com/zalando/SwiftMonkey
其实发现这个工具也有段时间没有更新了。
框架构成
简单介绍下整个工具的文件构成。
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()
}
}
当然以上只是一些简单的思路和测试修改,我们可以根据项目需要进行优化改进。