性能测试之

monkey

Posted by zsh on June 5, 2017

一.monkey:

1、monkey的实现原理:

Monkey是google提供的一个命令行工具,可以运行在模拟器或者物理设备中。monkey通过向系统发送按钮、手势、触摸屏输入等伪随机用户事件,对软件进行稳定性与压力测试。

当你执行adb shell monkey的时候,它到底干了什么。

monkey位于/system/bin目录下。内容为:
  # Script to start "monkey" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/monkey.jar
trap "" HUP
exec app_process $base/bin com.android.commands.monkey.Monkey $*
  
app_process是Android的系统启动进程,用于启动zygote和其他java进程:
if (zygote) {
       runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
   } else if (className) {
       runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
   }

adb这里是runtime执行com.android.internal.os.RuntimeInit来启动,位置在:

/system/framework/下面。有很多系统的包,其中有一个/system/framework/monkey.jar为monkey的所在包。

入口函数main在com.android.commands.monkey.Monkey

Application that injects random key events and other actions into the system.


public static void main(String[] args) {
        // Set the process name showing in "ps" or "top"
        Process.setArgV0("com.android.commands.monkey");

        int resultCode = (new Monkey()).run(args);
        System.exit(resultCode);
    }

入口函数很简单,直接跳到run这个方法,里面大概会做一下事情: ①处理命令行参数

privite int run(String[]args){
...
	if(!processOptions()){
		return -1;
	}
...
}


里面调用命令行处理函数processOptions,进去是很普通的读取命令行的参数然后一个个进行解析保存了

②处理要拉起的应用程序的Activity:

 我们在运行Monkey的时候,如果指定了“ -p 包名 ”,那么Monkey一定会拉起这个App的第一个Activity,借助Intent  

if (mMainCategories.size() == 0) {
            mMainCategories.add(Intent.CATEGORY_LAUNCHER);
            mMainCategories.add(Intent.CATEGORY_MONKEY);
        }

③根据命令行参数启动不同的事件源(有三种:脚本模式、网络模式、随机模式),根据这些事件的来源,由不同的类做处理

这个mEventSource有三种来源:

//脚本模式
            mEventSource = new MonkeySourceScript(mRandom, mScriptFileNames.get(0), mThrottle,
                    mRandomizeThrottle, mProfileWaitTime, mDeviceSleepTime);
     mEventSource = new MonkeySourceRandomScript(mSetupFileName,
                        mScriptFileNames, mThrottle, mRandomizeThrottle, mRandom,
                        mProfileWaitTime, mDeviceSleepTime, mRandomizeScript);
//网络模式,monkeyrunner的使用方式
 mEventSource = new MonkeySourceNetwork(mServerPort);
//默认模式,一般都使用随机事件
mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
                    mThrottle, mRandomizeThrottle, mPermissionTargetSystem);                    

④针对不同的事件源开始获取并执行不同的事件,循环处理

private int run(String[] args) {
    processOptions();//处理参数
    loadPackageLists();//加载黑白名单,可测的有效包名   
    getSystemInterfaces();//获取系统接口,都是系统的隐藏接口。
    //mAm = ActivityManagerNative.getDefault();
    //这里返回了一个ActivityManagerProxy对象,用来执行mangerservice接口。 
    //mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));    
    //上面,获取了系统窗口服务
    //mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));   
    getMainApps();//获取要执行的activity
    mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
                    mThrottle, mRandomizeThrottle, mPermissionTargetSystem);//产生一个随机事件
    ((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
    mEventSource.validate();//验证事件,并调整比例
    mNetworkMonitor.start();//监听网络变化
    crashedAtCycle = runMonkeyCycles();//monkey核心逻辑

}

monkey的事件列表类:

public abstract class MonkeyEvent {
    protected int eventType;
    public static final int EVENT_TYPE_KEY = 0;
    public static final int EVENT_TYPE_TOUCH = 1;
    public static final int EVENT_TYPE_TRACKBALL = 2;
    public static final int EVENT_TYPE_ROTATION = 3;  // Screen rotation
    public static final int EVENT_TYPE_ACTIVITY = 4;
    public static final int EVENT_TYPE_FLIP = 5; // Keyboard flip
    public static final int EVENT_TYPE_THROTTLE = 6;
    public static final int EVENT_TYPE_PERMISSION = 7;
    public static final int EVENT_TYPE_NOOP = 8;

    public static final int INJECT_SUCCESS = 1;
    public static final int INJECT_FAIL = 0;

    // error code for remote exception during injection
    public static final int INJECT_ERROR_REMOTE_EXCEPTION = -1;
    // error code for security exception during injection
    public static final int INJECT_ERROR_SECURITY_EXCEPTION = -2;

    public MonkeyEvent(int type) {
        eventType = type;
    }
    ...


monekey的核心执行逻辑;

while (!systemCrashed && cycleCounter < mCount) {
    //检查是否发生了ANR
    if (mRequestAnrBugreport){
        getBugreport("anr_" + mReportProcessName + "_");
        mRequestAnrBugreport = false;
    }
    //检查系统watchdog是否报告bug
     if (mRequestWatchdogBugreport) {
         System.out.println("Print the watchdog report");
         getBugreport("anr_watchdog_");
         mRequestWatchdogBugreport = false;
     }
    //检查是否发生了CRASH
    if (mRequestAppCrashBugreport){
        getBugreport("app_crash" + mReportProcessName + "_");
        mRequestAppCrashBugreport = false;
    }
    //检查bugreport报告生成
     if (mRequestPeriodicBugreport){
         getBugreport("Bugreport_");
         mRequestPeriodicBugreport = false;
     }
    //报告系统信息,ANR时出发
     if (mRequestDumpsysMemInfo) {
         mRequestDumpsysMemInfo = false;
         shouldReportDumpsysMemInfo = true;
     }
    //获取下一个随机时间
    MonkeyEvent ev = mEventSource.getNextEvent();    
    //注入事件
     int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
}


2、monkey与monkeyrunner

Monkey是Android中的一个命令行工具,可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试。Monkey测试是一种为了测试软件的稳定性、健壮性的快速的方法

monkeyrunner和monkey其实并无直接关系,但常常被认为是monkey的延伸。。monkeyrunner工具提供了一个API,使用此API写出的程序可以在Android代码之外控制Android设备和模拟器。通过monkeyrunner,您可以写出一个Python程序去安装一个Android应用程序或测试包,运行它,向它发送模拟击键,截取它的用户界面图片,并将截图存储于工作站上。monkeyrunner工具的主要设计目的是用于测试功能/框架水平上的应用程序和设备,或用于运行单元测试套件。

Monkey工具直接运行在设备或模拟器的adb shell中,生成用户或系统的伪随机事件流。monkeyrunner工具则是在工作站上通过API定义的特定命令和事件控制设备或模拟器,它支持,自己编写插件,控制事件,随时截图,简而言之,任何你在模拟器/设备中能干的事情,MonkeyRunner都能干,而且还可以记录和回放

二、Jenkins 集成 monkey

1.jenkins是基于java开发的一款持续集成工具,用于监控持续重复的工作,多用于搭建持续集成环境.

2.执行monkey测试有两种方式,一种是在模拟器中进行测试,另一种是在真机上进行测试

a)模拟器配置

①安装android模拟器插件

在jenkins的系统管理–》管理插件中,选择android emulator plugin进行安装。安装插件后,在已安装表中可以查看到该插件

②构建环境配置

安装了android emulator plugin之后,在构建环境中勾选“Run an Android emulator during build”选项。并对android模拟器进行配置。主要对android系统版本、屏幕分辨率、sd卡容量进行配置。我们可以依据自己的测试需求,进行相应的配置。

③构建配置

构建的配置,需要进行两项配置,Install android package和Run android monkey tester。 Install android package主要目的是把构建包安装到手机。 Run android monkey tester主要是启动monkey测试,并对运行参数进行配置。运行参数包括monkey测试过程中模拟的事件数量、事件间隔时间等。在高级选项中测试者可以依据自身需求添加更多配置。output filename选项对monkey测试结果文件名进行配置,默认情况下生成的测试结果存放在工作空间根目录下的monkey.txt文件中。

④构建后操作

构建后操作可以选择Publish android monkey tester result。 这样在构建完成之后,可以一目了然的看到monkey测试是否成功。该选项需要设置两个参数,分别是filename和set build result。参数Filename顾名思义是指定monkey测试生成的结果文件名称。如果没有指定filename的值,默认情况下会到该任务的根目录下面读取monkey.txt文件。如果在Run android monkey中对output filename进行了修改,那么filename应该与output filename保持一致。set build result选项是设置当monkey测试出现Crash或ANR,应该呈现的结果状态,默认情况下为unstable。

b)真机配置

1.参数化构建

由于monkey测试需要进行一系列参数配置,所以我们可以在jenkins的General中配置一些构建过程中需要使用的参数。例如,monkey运行的参数evencount,seed都可以在此进行输入配置,可以更加灵活的控制monkey测试。 同时,我们可以配置一个布尔变量,作为此次构建是否执行monkey测试的判断依据。这样便可以将普通构建任务和执行monkey测试的构建任务放在一个任务配置中,而不需要配置多个任务。如果该选项被勾选,那此次构建执行monkey测试,否则不执行。

2.构建

采用真机进行monkey测试,需要通过adb命令来启动monkey测试。所以需要在构建步骤中增加execute shell选项。然后在选项中,添加执行monkey测试的脚本。脚本主要执行两项任务,首先将构建的包安装到手机端,然后启动monkey测试。脚本中可以使用前面配置的参数,这样方便测试者依据不同的测试需求,构建不同的测试任务。 命令行执行monkey测试,基本语法: adb shell monkey [options]

event-count: 为必选参数,用于指定monkey测试过程中模拟的用户事件数量。

[options]主要参数如下:

-p packageName :该参数指定需要执行monkey测试的包名。如果需要测试多个包,需要添加多个-p参数,每个参数后带一个包名。

-s seed :该参数指定伪随机生成器的seed值。

-v: 指定monkey测试结果中输出log信息的详细程度,总共分为三个级别。

默认级别是0:-v,测试结果中只包含启动信息,测试完成信息和最终结果信息。

级别2:-v -v,打印测试过程中执行的一些信息,如模拟的用户点击事件。

级别3:-v -v -v,打印最详细的信息。

–throttle milliseconds:指定monkey模拟事件的前后间隔时间。通过这个选项可以减缓monkey的执行速度。如果不指定该选项,monkey模拟事件将会持续执行,事件间不会有停顿。

–ignore-crashes: 该参数指定monkey测试过程中是否忽略crash。如果设置了该参数,测试过程中如果发生了crash,monkey测试会继续执行。否则测试过程中遇到crash,停止测试。

–ignore-timeouts:该参数指定monkey测试过程中忽略ANR。如果测试过程中出现了ANR,将忽略此现象,继续执行monkey测试。

三、SwiftMonkey

1、

iOS 上的 monkey 工具。比如基于 UIAutomation 的 monkey,通过 copy from stackoverflow 和 copy from github 模式,不幸的是,iOS 和 Xcode 升级之后,UIAutomation 框架被砍掉了,于是很长时间 iOS 没有 monkey 的说法了。然后有一个基于 XCUITesting 框架的 monkey 工具 —swiftmonkey

2、

把 SwiftMonkey 和 SwiftMonkeyPaws 目录粘贴到你的项目目录下去。然后把他们两的 xcodeproj 拖到项目中去