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; }}
- Intellij插件开发:MonkeyMaster插件的实现(一)——命令行封装与调用
- Intellij插件开发:MonkeyMaster插件的实现(三)——写入日志的线程处理
- Intellij插件开发:MonkeyMaster插件的实现(二)——入门及UI搭建
- Intellij插件开发:一个辅助Monkey测试的插件——MonkeyMaster
- IntelliJ idea插件开发(一)
- 【Chrome】Chrome插件开发(一)插件的简单实现
- 微信公众平台开发初探(一)WeiPHP与它调用插件的过程
- JQuery——插件的开发和使用(一)
- Cacti的安装与插件开发(一)
- mosquitto鉴权插件的开发与说明(一)
- Maven之——插件目标及绑定、命令行调用插件、目标前缀(插件前缀解析策略)、插件解析运行机制
- intellij idea的插件开发小结
- 我的Firefox插件开发之旅(8)——插件的安装与更新
- 我的Firefox插件开发之旅(8)——插件的安装与更新
- jQuery 插件开发 封装
- QT实现浏览器插件 调用及回调 (一)
- Intellij IDEA插件开发入门
- intellij 开发调试elasticsearch插件
- git 配置多个SSH-Key
- GitLab简介 使用
- [LeetCode]05. Longest Palindromic Substring (动态规划)
- Jenkins-Join Plugin
- LeetCode 231. Power of Two
- Intellij插件开发:MonkeyMaster插件的实现(一)——命令行封装与调用
- GitLab/Git在AndroidStudio上的使用
- Jenkins-Throttle Concurrent Builds
- HDU 1284 钱币兑换问题
- web前端工程师面试--岗位介绍
- JAVA 的wait(), notify()与synchronized同步机制
- [POJ2750] 最大连续和 - 线段树
- python之函数enumerate()
- Git和Github简单教程