Intellij插件开发:MonkeyMaster插件的实现(一)——命令行封装与调用

来源:互联网 发布:toad for oracle mac 编辑:程序博客网 时间:2024/06/02 09:52

【转载请注明出处】
笔者:DrkCore (http://blog.csdn.net/DrkCore)
原文链接:http://blog.csdn.net/drkcore/article/details/54898981

Monkey测试工具打从一开始就是以命令行的形式存在的。就像其他常见的命令行工具一样,在连接上设备后你可以通过输入 adb shell monkey 来获得所有指令:

命令行

如果你想详细了解各个参数的作用请先看看这里。

在之后的篇幅中笔者将会向你介绍如何使用JAVA来构造Monkey语法,执行以及如何终止测试。你可以在笔者开源的工程的 core.plugin.monkey.core 包中找到所有的源代码。如果你觉得本文有任何不妥,请给留言~

一、 Monkey及其建造者

我们可以看到Monkey的可选参数很多,那么要封装这些参数最适合的莫过于建造者模式了。以下是 Monkey 类中建造者的核心代码:

public static class Builder implements Serializable, Cloneable {    private static final String KEY_MONKEY = "monkey";    public Monkey build() {        //CmdBuilder是用于简化字符串拼接的辅助类        CmdBuilder builder = new CmdBuilder();        builder.appendKey(KEY_MONKEY);        //仅当allowedPackages不空方法appendListEx才会将参数拼接到最终命令中        builder.appendListEx(KEY_ALLOWED_PACKAGE, allowedPackages);        builder.appendListEx(KEY_MAIN_CATEGORY, mainCategories);        //ignoreCrashes是Boolean类型的成员遍历,仅当不为null时方法appendBooleanEx才会将之拼接到最终命令中,下同        builder.appendBooleanEx(KEY_IGNORE_CRASHES, ignoreCrashes);        builder.appendBooleanEx(KEY_IGNORE_TIMEOUTS, ignoreTimeouts);        builder.appendBooleanEx(KEY_IGNORE_SECURITY_EXCEPTIONS, ignoreSecurityExceptions);        builder.appendBooleanEx(KEY_MONITOR_NATIVE_CRASHES, monitorNativeCrashes);        builder.appendBooleanEx(KEY_IGNORE_NATIVE_CRASHES, ignoreNativeCrashes);       builder.appendBooleanEx(KEY_KILL_PROCESS_AFTER_ERROR, killProcessAfterError);        builder.appendBooleanEx(KEY_HPROF, hprof);        builder.appendNumberEx(KEY_PCT_TOUCH, pctTouch);        builder.appendNumberEx(KEY_PCT_MOTION, pctMotion);        builder.appendNumberEx(KEY_PCT_TRACKBALL, pctTrackball);        builder.appendNumberEx(KEY_PCT_SYSKEYS, pctSyskeys);        builder.appendNumberEx(KEY_PCT_NAV, pctNav);        builder.appendNumberEx(KEY_PCT_MAJORNAV, pctMajornav);        builder.appendNumberEx(KEY_PCT_APPSWITCH, pctAppswitch);        builder.appendNumberEx(KEY_PCT_FLIP, pctFlip);        builder.appendNumberEx(KEY_PCT_ANYEVENT, pctAnyevent);        builder.appendNumberEx(KEY_PCT_PINCHZOOM, pctPinchzoom);        //仅当packageBlacklistFile不为null且不为空字符串时才将之拼接到最终的命令行中,下同        builder.appendStringEx(KEY_PKG_BLACKLIST_FILE, packageBlacklistFile);        builder.appendStringEx(KEY_PKG_WHITELIST_FILE, packageWhitelistFile);        builder.appendBooleanEx(KEY_WAIT_DBG, waitDbg);        builder.appendBooleanEx(KEY_DBG_NO_EVENTS, dbgNoEvents);        //部分语法比较特殊的参数特殊处理        if (scriptFiles != null) {            scriptFiles.forEach(s -> {                if (!TextUtil.isEmpty(s)) {                    if (!builder.contain(KEY_SCRIPTFILE)) {                        builder.appendParam(KEY_SCRIPTFILE, s);                    } else {                        builder.appendParam(KEY_SCRIPTFILE_TAG, s);                    }                }            });        }        builder.appendNumberEx(KEY_PORT, port);        builder.appendNumberEx(KEY_SEED, seed);        //LogLevel是枚举类,        //LogLevel.ALL.getParams = "-v -v -v"        //以此类推        if (logLevel != null) {            builder.appendKey(logLevel.getParams());        }        builder.appendNumberEx(KEY_THROTTLE, throttle);        builder.appendBooleanEx(KEY_RANDOMIZE_THROTTLE, randomizeThrottle);        builder.appendNumberEx(KEY_PROFILE_WAIT, profileWait);        builder.appendNumberEx(KEY_DEVICE_SLEEP_TIME, deviceSleepTime);        builder.appendBooleanEx(KEY_RANDOMIZE_SCRIPT, randomizeScript);        builder.appendBooleanEx(KEY_SCRIPT_LOG, scriptLog);        builder.appendBooleanEx(KEY_BUGREPORT, bugreport);        builder.appendBooleanEx(KEY_PERIODIC_BUGREPORT, periodicBugreport);        builder.append(count);        //构造Monkey实例        String cmd = builder.toString();        return new Monkey(cmd, times);    }}

接下来我们写个测试:

public class MonkeyTest {    @Test    public void testBuild(){        Monkey.Builder builder = new Monkey.Builder();        builder.setAllowedPackages("core.plugin.monkey")                .setIgnoreAll()//简化方法,忽略所有错误                .setKillProcessAfterError(true)//设置错误后杀死进程                .setPctMotion(12F)//设置事件比例                .setCount(123);        System.out.println(builder.build().getCmd());    }}

运行结果如下:

运行结果

二、 运行命令

在Java中可以通过 Runtime.getRuntime().exec() 方法来执行命令行并获得对应的输入输出流。为了方便后续的开发我们稍加封装:

//执行命令行并将输出写入指定的输出流protected final void execCommand(String cmd, @Nullable ByteArrayOutputStream out) throws Exception {    publishProgress("exec:" + cmd);    //执行命令行    Process process = RUNTIME.exec(cmd);    InputStream in = process.getInputStream();    if (in != null) {        if (out != null) {            BufferedReader reader = null;            try {                reader = new BufferedReader(new InputStreamReader(in));                String line;                while (!isCancelled() && (line = reader.readLine()) != null) {                    out.write(line.getBytes());                    out.write(LINE_SEPARATOR);                    publishProgress(line);                }            } finally {                IOUtil.close(reader);            }        } else {            //清空命令行的输入流            IOUtil.waste(in);        }    }    //清空命令行的错误输入流    IOUtil.waste(process.getErrorStream());    //等待命令行结束    process.waitFor();}

Monkey命令其实是属于 Adb 工具中的一个模块,所以想要执行Monkey就要拼接上 Adb 上的部分命令,如下:

//确保Adb进程已经启动protected final void ensureAdb(@Nullable ByteArrayOutputStream out) throws Exception {    execCommand("cmd.exe /c adb start-server", out);}//执行Adb Shell命令,暂时只支持Win平台protected final void execShell(String shellCmd, String device, @Nullable ByteArrayOutputStream out) throws Exception {    ensureAdb();    shellCmd = "cmd.exe /c adb -s " + device + " shell " + shellCmd;    execCommand(shellCmd, out);}

接着我们封装一个执行命令行的基类 MonkeyTask :

//执行异步逻辑的基类,实现方式上参考了Android的AsyncTaskpublic class MonkeyTask extends CommandTask<MonkeyTask.Params, ByteArrayOutputStream> {    //重复执行命令直到手动停止    private static final int INFINITE = Monkey.INFINITE;    public static class Params {        //Monkey命令            String cmd;        //命令执行的次数        int times;        //执行的设备码        String device;    }    @Override    protected ByteArrayOutputStream doInBack(Params params) throws Exception {        int leftTimes = params.times;        ByteArrayOutputStream out = new ByteArrayOutputStream();        while (!isCancelled() && (params.times == Monkey.INFINITE || leftTimes-- > 0)) {            execShell(params.cmd, params.device, out);        }        return out;    }}

由于执行Monkey测试需要设备码,所以我们还要写获取设备码的逻辑,FindDevicesTask的源码如下:

public class FindDevicesTask extends CommandTask<Void,List<String>> {    @Override    protected List<String> doInBack(Void aVoid) throws Exception {        ensureAdb();        ByteArrayOutputStream out = new ByteArrayOutputStream();        execCommand("cmd.exe /c adb devices",out);        String info = out.toString();        String[] lines = info.split("\r|\n");        //命令行运行结果格式如下        //List of devices attached        //127.0.0.1:62001 device        int len = lines.length;        List<String> devices = new ArrayList<>(len);        for (int i = 0; i < len; i++) {            String line = lines[i];            if (line != null && !"".equals(line) && !"List of devices attached".equals(line)) {                int idx = line.indexOf('\t');                if (idx != -1) {                    line = line.substring(0, idx);                }                devices.add(line);            }        }        return devices;    }}

接着让我们写一个单元测试验证下结果:

public class MonkeyTaskTest {    @Test    public void testMonkey() throws Throwable {        //向屏幕随机发送100个命令,日志等级最高        Monkey monkey = new Monkey.Builder().setCount(100).setLogLevel(LogLevel.ALL).build();        System.out.println(monkey.getCmd());        //获取第一个设备        List<String> devices = new FindDevicesTask().exec().get();        String device = DataUtil.getFirstQuietly(devices);        System.out.println(device);        //执行,并将日志输出到控制台        new MonkeyTask().exec(new MonkeyTask.Params(monkey.getCmd(), 1, device)).get().writeTo(System.out);    }}

执行结果如下:
测试运行

三、 终止Monkey测试

相信不少小伙伴在第一次使用Monkey测试的时候都曾手贱将执行的count设置为一个超大的数,结果手机/模拟器就像是一只吃了炫迈的猴子一样根本停不下来。无数的事件如潮水般涌向屏幕上的各个控件,任你手速如何之快也回天乏术,万般无奈之下你肯定想起来计算机界最古老的万能咒语——重启下试试——然后你发现你没办法关机,因为关机的确认界面一出现就被Monkey取消了——最终你无奈地扣下了设备的电池。

悲剧之后你开始思考如何能够更加优雅地终止Monkey测试,这对于写码多年的你并没有什么难度——Monkey测试本身只不过是运行环境上的一条进程,只要杀死这个进程自然就可以终止测试了。

public class KillMonkeyTask extends CommandTask<String, Void> {    @Override    protected Void doInBack(String device) throws Exception {        ensureAdb();        ByteArrayOutputStream out = new ByteArrayOutputStream();        //使用adb命令查找Monkey进程        //注意要加上双引号        execShell("\"ps|grep monkey\"", device, out);        String info = out.toString();        //截取从进程信息中截取pid        //root      9542  76    1234904 40084 ffffffff b7726369 S com.android.commands.monkey        String[] items = info.split(" ");        String pid = null;        for (int i = 0, count = 2, len = items.length; i < len; i++) {            String item = items[i];            if (item != null && !"".equals(item)) {                if (--count == 0) {                    pid = item;                    break;                }            }        }        //杀死进程        if (pid != null) {            execShell("kill " + pid, device);        }        return null;    }}
0 0
原创粉丝点击