制作网站riverppt制作网站推荐
制作网站river,ppt制作网站推荐,做漂亮的网站,江苏赛华建设监理有限公司网站1. 为什么我们需要“聪明”的Monkey#xff1f;
说到移动应用的稳定性测试#xff0c;很多测试同学第一时间想到的就是Monkey。这个Android SDK自带的“猴子”工具#xff0c;确实是个好东西#xff0c;它能在你的应用里上蹿下跳#xff0c;随机点击、滑动、输入#xff…1. 为什么我们需要“聪明”的Monkey说到移动应用的稳定性测试很多测试同学第一时间想到的就是Monkey。这个Android SDK自带的“猴子”工具确实是个好东西它能在你的应用里上蹿下跳随机点击、滑动、输入帮你发现那些藏在角落里的崩溃和ANR应用无响应。但用久了大家心里都清楚这只“猴子”有点“傻”。我刚开始用Monkey的时候就踩过不少坑。比如我明明想测试的是我们自家的电商App结果Monkey执行到一半一个随机点击打开了系统的设置页面或者跳到了手机里安装的其他App里。更头疼的是我们App内部有一些页面比如调试工具页、第三方地图页这些页面本身可能就不稳定或者不是我们本次测试的重点。让Monkey在这些非核心页面上“撒野”不仅浪费了宝贵的测试时间产生的崩溃日志还会严重干扰我们对核心功能稳定性的判断。测试报告出来一看崩溃次数不少但一大半都不是我们想关注的业务模块这测试效果就大打折扣了。所以我们真正需要的是一只“戴着镣铐跳舞”的、聪明的Monkey。我们希望它能在一个我们划定的“安全区”内活动这个安全区就是我们App的核心页面集合。这就是“基于Activity白名单的智能Monkey定向测试”方案的核心思想。它不是要取代传统的Monkey而是给它加上一个“导航”和“纠偏”系统确保测试行为始终聚焦在我们关心的业务场景上。这对于需要精准评估某个新版本、某个特定功能模块比如支付流程、商品详情页稳定性的测试场景来说价值巨大。你不用再大海捞针似的从一堆无关的崩溃日志里找问题测试效率和结论的准确性都会大幅提升。2. 方案基石如何让任意Activity都能被外部启动要想控制Monkey的活动范围我们首先得有能力在它“跑偏”的时候把它拉回我们指定的页面。这听起来简单不就是用adb shell am start命令打开一个Activity吗但实际操作过你就会发现事情没那么简单。出于安全考虑Android系统默认不允许随便一个外部命令就启动应用内的任意页面。如果你直接对一个普通的App执行adb shell am start -n com.example.app/.MainActivity很可能会看到类似Permission Denial的错误。这是因为目标Activity在它的AndroidManifest.xml文件里没有设置android:exportedtrue属性。这个属性就像房间的门锁设置为true意味着允许从外面其他应用或系统打开这扇门。难道我们要为了测试去修改App的源码给几十上百个Activity都加上exportedtrue吗这显然不现实而且会引入安全风险。好在我们有一个更优雅的“后门”方案专门用于开发和测试阶段。这个方案的核心思路是利用Android Gradle构建系统的灵活性在编译Debug版本时动态地修改生成的AndroidManifest.xml文件为所有Activity临时添加exportedtrue的属性。具体怎么做呢你不需要动一行Java代码。只需要在你App模块的build.gradle文件里添加一段Gradle脚本。我以最常见的app模块为例找到android { ... }这个代码块在里面添加一个applicationVariants.all的配置android { // ... 其他配置 ... applicationVariants.all { variant - if (variant.buildType.name debug) { variant.outputs.each { output - def processManifest output.getProcessManifestProvider().get() processManifest.doLast { task - def manifestFile task.getManifestOutputFile() if (manifestFile.exists()) { def xml new XmlParser(false, false).parse(manifestFile) xml.application.activity.each { activity - // 为每个Activity节点添加或修改 exported 属性 if (activity.attributes().get(android:exported) null) { activity.attributes().put(android:exported, true) } } def printer new XmlNodePrinter(new PrintWriter(new FileWriter(manifestFile))) printer.setPreserveWhitespace(true) printer.print(xml) } } } } } }这段脚本的作用是在构建Debug变体时在所有任务完成后doLast找到最终生成的合并后的Manifest文件用XML解析器打开它遍历所有的activity标签如果发现某个activity没有android:exported属性就给它加上并设置为true。这样一来你打出来的Debug包内部的所有Activity就都“暂时对外开放”了。切记这个操作只适用于测试专用的Debug包绝对不要用在Release发布包上否则会带来严重的安全漏洞。完成配置后同步一下Gradle然后用Android Studio重新安装Debug包到测试手机上。之后你就可以用adb shell am start -n 包名/Activity全类名这个命令自由地启动App内的任何一个页面了。这是我们整个智能Monkey方案的“钥匙”。3. 构建智能Monkey测试框架有了启动任意页面的能力我们就可以开始搭建智能Monkey的测试框架了。直接裸跑adb shell monkey命令太原始我们需要一个能统筹调度、能监控状态、能执行纠偏的“大脑”。我推荐使用Java Appium/UI Automator2 多线程的方式来构建这样灵活性和可控性最强。3.1 定义你的“安全区”Activity白名单首先我们要明确告诉框架哪些页面是Monkey可以活动的“安全区”。最好的方式就是用一个白名单列表来管理。我习惯创建一个专门的配置类比如叫TestConfig.java。public class TestConfig { // 被测应用的包名 public static final String TARGET_PACKAGE com.yourcompany.yourapp; // Activity白名单列表 // 格式 包名/Activity全类名 public static final String[] ACTIVITY_WHITELIST { TARGET_PACKAGE /.MainActivity, TARGET_PACKAGE /.home.HomeFragmentActivity, TARGET_PACKAGE /.product.ProductDetailActivity, TARGET_PACKAGE /.cart.ShoppingCartActivity, TARGET_PACKAGE /.order.PaymentActivity, TARGET_PACKAGE /.user.UserCenterActivity // ... 添加所有你希望测试的核心页面 }; // Monkey测试参数 public static final int MONKEY_EVENT_COUNT 50000; public static final long MONKEY_THROTTLE_MS 300; // 事件间隔300毫秒 public static final long PAGE_CHECK_INTERVAL_MS 10000; // 每10秒检查一次页面 }这个白名单就是我们的测试边界。在实际项目中你可以根据测试需求灵活调整比如这次专项测试支付流程就把支付相关的页面加进去下次测试商品浏览就换成商品相关的页面。3.2 核心逻辑页面状态监控与纠偏这是整个方案的“智能”所在。我们需要一个能定时检查当前手机前台是哪个页面并在页面“越界”时将其拉回白名单的守护进程。第一步获取当前Activity。通过ADB命令adb shell dumpsys window windows可以获取当前窗口信息从中可以解析出最顶层的Activity。我们可以写一个工具方法来处理import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.regex.Matcher; import java.util.regex.Pattern; public class AdbUtils { private String deviceId; // 设备序列号多设备时需指定 public String getCurrentActivity() throws Exception { // 命令适配了多设备情况 String command String.format(adb %s shell dumpsys window windows | grep -E mCurrentFocus|mFocusedApp, deviceId.isEmpty() ? : -s deviceId); Process process Runtime.getRuntime().exec(command); BufferedReader reader new BufferedReader(new InputStreamReader(process.getInputStream())); String line; Pattern pattern Pattern.compile(Target_PACKAGE /[^\\s\\}]); while ((line reader.readLine()) ! null) { Matcher matcher pattern.matcher(line); if (matcher.find()) { return matcher.group(); } } reader.close(); return null; // 未找到目标应用的Activity } }第二步纠偏逻辑。如果当前Activity不在白名单里我们就随机从白名单中选一个用am start命令跳转过去。public class SmartMonkeyController { private AdbUtils adbUtils; private Random random new Random(); public void checkAndCorrectActivity() { try { String currentActivity adbUtils.getCurrentActivity(); System.out.println([ new Date() ] 当前页面: currentActivity); if (currentActivity null || !isInWhitelist(currentActivity)) { System.out.println(警告页面偏离白名单执行纠正...); String targetActivity TestConfig.ACTIVITY_WHITELIST[random.nextInt(TestConfig.ACTIVITY_WHITELIST.length)]; String command String.format(adb %s shell am start -n %s, adbUtils.getDeviceIdForCmd(), targetActivity); executeShellCommand(command); System.out.println(已跳转至: targetActivity); } else { System.out.println(页面正常位于白名单内。); } } catch (Exception e) { e.printStackTrace(); } } private boolean isInWhitelist(String activity) { for (String whiteActivity : TestConfig.ACTIVITY_WHITELIST) { if (whiteActivity.equals(activity)) { return true; } } return false; } private void executeShellCommand(String command) throws Exception { Process process Runtime.getRuntime().exec(command); process.waitFor(); // 等待命令执行完成 } }3.3 双线程驱动Monkey执行与监控守护现在我们需要让“猴子跑”和“眼睛看”这两件事同时进行。这就需要用到多线程。一个线程我们叫它MonkeyRunner专门负责执行原始的Monkey命令施加压力另一个线程叫它ActivityWatcher则像一个忠诚的卫士定时唤醒检查页面状态并执行纠偏。public class SmartMonkeyEngine { public void startTest() { // 线程1执行Monkey Thread monkeyThread new Thread(() - { try { String monkeyCommand String.format(adb shell monkey -p %s --throttle %d --ignore-crashes --ignore-timeouts -v %d, TestConfig.TARGET_PACKAGE, TestConfig.MONKEY_THROTTLE_MS, TestConfig.MONKEY_EVENT_COUNT); System.out.println(启动Monkey命令: monkeyCommand); executeShellCommand(monkeyCommand); } catch (Exception e) { e.printStackTrace(); } }); // 线程2监控与纠偏 Thread watcherThread new Thread(() - { SmartMonkeyController controller new SmartMonkeyController(); while (true) { controller.checkAndCorrectActivity(); try { // 每隔一段时间检查一次 Thread.sleep(TestConfig.PAGE_CHECK_INTERVAL_MS); } catch (InterruptedException e) { break; // 被中断时退出循环 } } }); // 启动线程 monkeyThread.start(); watcherThread.start(); // 等待Monkey线程结束测试完成 try { monkeyThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } // Monkey结束后中断监控线程 watcherThread.interrupt(); System.out.println(智能Monkey测试执行完毕。); } }这样一个最基本的智能Monkey框架就搭起来了。执行startTest()方法后Monkey会在你的App里疯狂操作而监控线程会像巡逻兵一样每隔10秒这个时间可以配置检查一下当前页面。一旦发现Monkey跑到了白名单之外的页面比如系统相册、浏览器它会立刻执行am start命令随机跳转回白名单里的某个页面从而把测试焦点牢牢锁死在我们的核心功能上。4. 进阶优化与实战技巧上面的方案已经能解决核心问题但在实际项目中使用我们还可以做得更精细、更强大。这里分享几个我踩过坑后总结的优化点。4.1 白名单的动态管理与页面权重静态的白名单列表有时不够灵活。比如我们可能希望测试初期更多地停留在首页和列表页后期则更侧重于详情页和支付页。我们可以实现一个带权重的白名单。public class WeightedActivity { String activityName; int weight; // 权重数值越高被随机选中的概率越大 // ... getter setter ... } // 在纠偏选择页面时不再简单随机而是根据权重进行加权随机 public String selectActivityByWeight(ListWeightedActivity list) { int totalWeight list.stream().mapToInt(WeightedActivity::getWeight).sum(); int randomNum random.nextInt(totalWeight); int currentSum 0; for (WeightedActivity wa : list) { currentSum wa.getWeight(); if (randomNum currentSum) { return wa.getActivityName(); } } return list.get(0).getActivityName(); // 兜底 }更进一步我们可以让框架支持从外部配置文件如JSON、YAML读取白名单和权重这样不用修改代码就能调整测试策略。甚至可以实现一个简单的HTTP接口在测试运行时动态增删白名单条目实现真正的“动态定向”。4.2 增强监控不只是Activity只监控Activity有时还不够。比如你的App里可能用了一个WebView页面这个WebView的Activity叫WebViewActivity但里面加载的H5页面却千差万别。如果Monkey在WebView里乱点跳转到了外部的第三方H5页面从Activity层面看它还在WebViewActivity里但实际上已经“越界”了。这时候我们需要更细粒度的监控。可以结合Appium的driver.getPageSource()获取当前页面的UI控件树通过查找特定的控件ID、文字内容或XPath来判断是否处于我们允许的Web页面内。这需要更复杂的规则引擎但能提供更精准的控制。// 伪代码结合Appium进行WebView内容判断 public boolean isInAllowedWebPage(AppiumDriver driver) { String pageSource driver.getPageSource(); // 判断页面源码中是否包含我们预期的关键元素比如“商品详情”、“立即购买”按钮 return pageSource.contains(id\btn_buy\) || pageSource.contains(text\确认支付\); }4.3 异常处理与测试数据构造稳定性测试往往需要长时间运行比如12小时、24小时。在这个过程中App可能会发生崩溃、ANR或者因为Monkey的操作导致登录态失效。一个健壮的智能Monkey框架需要能处理这些异常。崩溃/ANR恢复在监控线程中除了检查页面还可以定期检查App进程是否存在adb shell ps | grep 包名。如果发现进程没了说明崩溃了可以尝试先收集日志然后重新启动App并执行一些初始化操作比如自动登录。自动登录在测试开始前或者检测到登录页面时用脚本自动输入测试账号密码完成登录。这需要你提前准备好测试账号并将登录操作封装成函数。关键事件记录不要只记录崩溃。可以在代码中添加钩子记录下每次页面纠偏的时间、纠偏前的页面、纠偏后的页面。这些日志对于分析Monkey的“越狱”规律非常有帮助。你可能会发现Monkey总是从某个特定的按钮跳转到系统设置那可能就是这个按钮的点击事件处理有问题。4.4 与CI/CD管道集成智能Monkey测试不应该是一个手动执行的玩具。我们可以把它集成到持续集成CI流程中比如Jenkins、GitLab CI。每天晚上定时对开发中的dev分支构建包进行一轮智能Monkey测试第二天早上就能看到测试报告包括总执行事件数、崩溃次数、ANR次数、发生的页面、以及详细的Logcat和崩溃堆栈信息。在CI脚本中大致流程如下拉取最新代码编译生成带exportedtrue的Debug包。安装到连接的测试机或启动模拟器。运行我们的智能Monkey Java程序。测试完成后自动拉取设备中的日志、截图如果实现了的话和生成的测试报告。解析日志将关键结果如是否有新的崩溃汇总并通知到团队如通过钉钉、企业微信机器人。5. 方案对比与选型思考看到这里你可能会问市面上已经有一些现成的Monkey增强工具比如Maxim、AppCrawler等为什么还要自己折腾这套方案呢这里我简单做个对比帮你理清思路。原生Monkey完全随机不可控测试噪声大。适合最基础的、对测试范围无要求的压力测试。Maxim等工具它们通过接入无障碍服务或使用其他黑科技能够实现更智能的事件注入如识别控件进行点击在一定程度上可以控制测试路径。但它们的学习成本较高规则配置复杂且对App的UI结构有较强依赖当UI改动大时维护成本不低。本文的Activity白名单方案它的核心优势在于简单、直接、底层。它不关心UI控件是什么只关心最根本的页面Activity维度。实现成本低维护成本也低只需要维护一个Activity列表特别适合将Monkey测试作为“稳定性守护”的长期任务来运行。它的目的不是做功能遍历而是确保在核心页面范围内App能承受住长时间的压力而不出问题。所以我的建议是如果你需要的是对核心业务场景进行高强度、长时间、无人值守的稳定性“轰炸”测试那么基于Activity白名单的智能Monkey方案是一个非常务实且高效的选择。它就像给你的Monkey测试装上了GPS和电子围栏让它既能发挥“暴力测试”的威力又不至于破坏测试靶场。在实际项目中我将这套方案运行了超过三个月每晚对核心版本进行8小时测试。它成功地过滤掉了超过80%因跳转到外部页面或非核心调试页面而产生的无效崩溃报告使得开发人员能够集中精力处理那些真正发生在商品浏览、下单、支付流程中的稳定性问题测试团队的信任度和效率都得到了显著提升。刚开始搭建时可能会花点时间但一旦跑起来它就是一个非常可靠的“自动化守夜人”。