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 可得到如下信息


    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<String> categories = new ArrayList<String>();
        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<String, Object>(), 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<ActivityConfigureBean> 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包

image.png

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

image.png

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


image.png
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如果没有相关资源必须以空字符串占位

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容