Android自动化测试之页面覆盖比例

需求

自动化测试过程中提高指定模块的测试率:每一个版本的迭代,需要对这个版本进行重点测试,这时就需要配置页面的覆盖比例,重点模块重点测试

构想

利用MonkeyRunner干预Monkey的事件流,在Monkey自动化测试的过程中,在特定的情境下触发MonkeyRunner指定跳转页面,Monkey继续模拟点击

环境

Monkey相关

安装Android SDK 并配置环境变量
http://pan.baidu.com/s/1jIlifbS

MonkeyRunner相关

jdk、android sdk、python编译器(如果是使用命令行运行python脚本的方式)

工具

Monkey

功能:模拟用户触摸屏幕、滑动Trackball、 按键等操作来对设备上的程序进行压力测试,简单的讲就是,瞎点

简介:

  • Monkey程序由Android系统自带,使用Java诧言写成,在Android文件系统中的存放路径是: /system/framework/monkey.jar
  • Monkey.jar程序是由一个名为“monkey”的Shell脚本来启动执行,shell脚本在Android文件系统中 的存放路径是:/system/bin/monkey
  • 启动方式
    (1) 可以通过PC机CMD窗口中执行: adb shell monkey {+命令参数}来进行Monkey测试
    (2) 在PC上adb shell 进入Android系统,通过执行 monkey {+命令参数} 来进行Monkey 测试
    (3) 在Android机或者模拟器上直接执行monkey 命令,可以在Android机上安装Android终端模拟器
MonkeyRunner

功能:它提供一个API,运用该API编写的程序可以不用通过android代码来直接控制Android设备和模拟器,我们可以写一个Python程序对android应用程序或测试包进行安装,运行,发送模拟击键

vs Monkey:Monkey产生的事件都是随机的,不可控的;Monkeyrunner既能按照用户的需求来产生特定序列的特定事件,还能截屏保存和比较图片,编写简单。借助于python,也能实现强大的功能。

DO IT

正文开始!

掌控MonkeyRunner

目的:
(1) 监听应用是否跳出,如果跳出则跳往指定的页面
(2) 指定时间间隔,每过一段时间跳转到指定页面

  • 监听跳出
    首先,执行cmd指令adb devices 可得到如下信息


    image.png

    其中3488170为当前与pc相连的手机设备号

    执行指令:adb -s 3483170 shell dumpsys activity top 可得到如下信息


    Android自动化测试之页面覆盖比例_第1张图片
    image.png

    其中第二行的信息则是当前设备中栈顶activity的信息
    于是,综合以上信息则可判断当前应用是否跳出
    接下来,java代码的实现:

  /**
     * 判断某个设备口袋贵金属是否在前台
     *
     * @param deviceId
     * @return
     */
    public boolean watchActivity(String deviceId) {
        boolean isAlive = false;
        Runtime run = Runtime.getRuntime();
        Process proc;
        try {
//            proc = run.exec("adb shell dumpsys activity top");
            //监听指定的设备
            proc = run.exec("adb -s " + deviceId + " shell dumpsys activity top");
            BufferedReader in = new BufferedReader(new InputStreamReader(
                    proc.getInputStream()));
            StringBuffer stringBuffer = new StringBuffer();
            String line = null;
            while ((line = in.readLine()) != null) {
                if (line.contains(pakageName)) {
                    isAlive = true;
                    break;
                }
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return isAlive;
    }
 /**
     * 对多设备监听跳出,并执行跳转
     */
    public void checkForStartActivity() {
        System.out.println("----checkForStartActivity------");
        Runtime run = Runtime.getRuntime();
        Process proc;
        try {
            proc = run.exec("adb devices");
            BufferedReader in = new BufferedReader(new InputStreamReader(
                    proc.getInputStream()));
            String line = null;
            while ((line = in.readLine()) != null) {//遍历所有的设备id
                if ((line.contains("device") || line.contains("offline")) && !line.contains("devices")) {  //排除第一行和最后一行的数据
                    String deviceId = line.replace("device", "").trim();
                    if (line.contains("device")) {
                        deviceId = line.replace("device", "").trim();
                    }
                    if (line.contains("offline")) {
                        deviceId = line.replace("offline", "").trim();
                    }
                    System.out.println("DeviceId=" + deviceId + "----isAlive=" + watchActivity(deviceId));
                    if (!watchActivity(deviceId)) {  // 对某一设备监听是否跳出
                        startActivityByJava(deviceId);  // 跳出则跳往指定的页面
                    }
                }
            }
          in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 /**
     * java 启动monkeyrunner
     *
     * @param deviceId 设备id
     */
    public void startActivityByJava(String deviceId) {
        if (adb == null) {
            adb = new AdbBackend();
            //这里需要注意一下adb的类型
        }
        //      参数分别为自己定义的等待连接时间和设备id
        device = (AdbMonkeyDevice) adb.waitForConnection(1000, deviceId);
        //添加启动权限
        String action = "android.intent.action.MAIN";
        Collection categories = new ArrayList();
        categories.add("android.intent.category.LAUNCHER");
        //              启动要测试的主界面
        if (device != null) {
            for (int i = 0; i < activityPaths.length; i++) {
                device.startActivity(null, action, null, null, categories,
                        new HashMap(), activityPaths[i], 0);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            device.dispose();  // 需要dispose操作 否则无法再次启动activity
        } else {
            startActivityByPython(runnerPath, pythonPath);   //如果java启动失败就python启动
        }
    }

上述代码为启动MonkeyRunner的核心代码,其中为java和python的双启动方式(java失败则启动python)
由于python的启动是辅助的启动方式,这里不作详细介绍,读者可自行百度或参考:
http://www.cnblogs.com/lynn-li/p/5885001.html
至于java的启动方式则略显复杂,详细可参考:
http://www.cnblogs.com/nuliniaoboke/archive/2012/11/23/2784385.html
关于高版本java对MonKeyRunner的调用:
http://blog.csdn.net/dyllove98/article/details/8797009

  • 定时器循环强制跳转
    目的:每过一段时间强制跳转到指定页面,以达到重点模块重点测试的效果
   /**
     * 强制跳转activity
     *
     * @param
     */
    public void forceJumpActivity() {
        System.out.println("----forceJumpActivity------");
        Runtime run = Runtime.getRuntime();
        Process proc;
        try {
            proc = run.exec("adb devices");
            BufferedReader in = new BufferedReader(new InputStreamReader(
                    proc.getInputStream()));
            String line = null;
            while ((line = in.readLine()) != null) {//遍历所有的设备id
                if ((line.contains("device") || line.contains("offline")) && !line.contains("devices")) {  //排除第一行和最后一行的数据
                    String deviceId = line.replace("device", "").trim();
                    if (line.contains("device")) {
                        deviceId = line.replace("device", "").trim();
                    }
                    if (line.contains("offline")) {
                        deviceId = line.replace("offline", "").trim();
                    }
                    startActivityByJava(deviceId);
                }
            }
          in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  static class MyTask1 extends java.util.TimerTask {
        public void run() {
            int which = new Random().nextInt(activitys.size());
            System.out.println("force jump activity:" + activitys.get(which));
            activityPaths[0] = pakageName + "/." + activitys.get(which);
            util.setActivityPaths(activityPaths);
            util.forceJumpActivity();
        }
    }
  • 参数配置
    以main函数作为入口,根据项目的需求定义四个参数
   /**
     * @param args
     */
    public static void main(String[] args) {  //pakageName pythonPath runnerPath activityPaths
        // TODO Auto-generated method stub
        pakageName = args[0];
        String pythonPath = args[1];
        String runnerPath = args[2];
        String json = args[3];
        List list = new ArrayList<>();
        JSONArray array = JSONArray.fromObject(json);
        String activityPaths[] = new String[array.size()];
        for (int i = 0; i < array.size(); i++) {
            JSONObject object = (JSONObject) array.get(i);
            ActivityConfigureBean bean = (ActivityConfigureBean) JSONObject.toBean(object, ActivityConfigureBean.class);
            System.out.println("activity=" + bean.getActivity());
            list.add(bean);
            for (int j = 0; j < bean.getScale(); j++) {
                activitys.add(bean.getActivity());
            }
            activityPaths[i] = pakageName + "/." + bean.getActivity();
            System.out.println("bean.activity=" + bean.getActivity());
        }

        util = new MonkeyRunnerUtil.Builder().pakageName(pakageName).pythonPath(pythonPath).runnerPath(runnerPath).activityPaths(activityPaths).build();


        Timer timer = new Timer();
        timer.schedule(new MyTask(), 1000, 5000); //5秒一次监听跳出
        timer.schedule(new MyTask1(), 1000, totalTime /activityPaths.length); //指定时间间隔跳转指定页面 时间间隔由配置的页面数确定

//        util.startActivityByJava("3483170");
//        util.startActivityByPython(util.runnerPath,util.pythonPath);
//        System.out.println("isAlive="+util.watchActivity());

    }

其中,
pakagename:所要运行程序的包名
pythonpath:python脚本在本地的路径 一般为空字符串
runnerpath:monkeyrunner在本地的路径 一般为空字符串
json:页面的覆盖率配置,需要严格的json数据格式,如:“[ { \”activity\”: \”MainActivity\”, \”scale\”: 1 }
, { \”activity\”: \”active.me.LoginActivity\”, \”scale\”: 2 }, { \”activity\”: \”LoadingActivity\”, \”scale\”: 3
}]”,activity指页面,scale指权重

    static class MyTask extends java.util.TimerTask {
        public void run() {
//            int which = new Random().nextInt(activitys.size());
            System.out.println("check out");
//            activityPaths[0] = pakageName + "/." + activitys.get(which);
//            util.setActivityPaths(activityPaths);
            util.checkForStartActivity();   //监听跳出则跳转指定的页面
        }
    }
static class MyTask1 extends java.util.TimerTask {
        public void run() {
            int which = new Random().nextInt(activitys.size());   //随机取数 以达到不同的覆盖比例
            System.out.println("force jump activity:" + activitys.get(which));
            activityPaths[0] = pakageName + "/." + activitys.get(which);
            util.setActivityPaths(activityPaths);
            util.forceJumpActivity();
        }
    }
  • 导出成jar
    项目是以Android studio中Module为Android Library的形式存在,导出jar包:
    首先,Module下的build文件中配置
task makeJar(type: Copy) {
    delete 'build/libs/cmdlib.jar'
    from('build/libs/')
    into('build/libs/')
    include('cmdlib.jar')
    rename ('cmdlib.jar', 'monkeycontroller.jar')
}
makeJar.dependsOn(build)

接着通过终端指令 gradlew makeJar 可导出jar包

Android自动化测试之页面覆盖比例_第2张图片
image.png

此jar当前还不能使用java -jar 直接使用,还需要两步操作
(1) 拷贝出jar包以及项目中的libs文件夹到pc本地的同一目录下

image.png

(2) 利用WinRAR找到清单文件并修改更新。注意清单文件的修改规范,最后必须有两行以上的空行,否则无法识别Class-Path


Android自动化测试之页面覆盖比例_第3张图片
image.png
Android自动化测试之页面覆盖比例_第4张图片
image.png

此时的jar便可以通过java -jar的指令使用了

monkeycontroller.jar的使用
  • 开启monkey
    打开cmd指令窗口,执行指令例如
    adb shell monkey -p org.sojex.finance -s 500 –throttle 1000 –ignore-crashes –ignore-timeouts -v 10000000

  • 开启monkeycontroller
    打开一个新的cmd指令窗口,cd到monkeycontroller.jar的目录下
    执行指令例如
    java -jar monkeycontroller.jar <”pakagename”> <”pythonpath”> <”runnerpath”> <”json”>
    其中
    pakagename:所要运行程序的包名
    pythonpath:python脚本在本地的路径 一般为空字符串
    runnerpath:monkeyrunner在本地的路径 一般为空字符串
    json:页面的覆盖率配置,需要严格的json数据格式,参考实例:
    java -jar monkeycontroller.jar org.sojex.finance “” “” “[ { \”activity\”: \”MainActivity\”, \”scale\”: 1 }
    , { \”activity\”: \”active.me.LoginActivity\”, \”scale\”: 2 }, { \”activity\”: \”LoadingActivity\”, \”scale\”: 3
    }]”
    其中
    json字符串中,key为 “activity” 的 value 对应为activity的名称,key为 “scale” 对应的 value 为需要启动的权重比

  • 注意事项
    参数顺序不可变,严格参照上述实例顺序,其中pythonpath和runnerpath如果没有相关资源必须以空字符串占位

你可能感兴趣的:(Android自动化测试之页面覆盖比例)