zoukankan      html  css  js  c++  java
  • swiftmonkey 源码剖析及二次开发思路

    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()
                
            }
        }
    

      

    当然以上只是一些简单的思路和测试修改,我们可以根据项目需要进行优化改进。
  • 相关阅读:
    final,static,this,super 关键字总结
    Java基础知识(三)
    Java基础知识(二)
    Java基础知识(一)
    MyBatis重要核心概念
    Mybatis执行SQL的完整过程及四大组件介绍
    Mybatis之plugin插件设计原理
    Spring MVC 9大组件概述
    Log4j的使用
    Git的介绍及使用
  • 原文地址:https://www.cnblogs.com/dreamyu/p/11280030.html
Copyright © 2011-2022 走看看