Monkey测试这个活儿,圈子里一直有两种声音。一种觉得它就是个“猴子”在屏幕上乱点,属于毫无技术含量的稳定性测试,跑就完了;另一种则认为它真能炸出一堆偶现崩溃、ANR和内存问题,是发版前最省心的兜底手段。我自己做移动端质量保障这么多年,更偏向后者,但前提是——你得会正确地用它。
很多人跑Monkey就是一行adb shell monkey 10000,跑完看个结果就收工。这其实浪费了Android官方这个工具大半的能力。今天这篇就把Monkey测试从原理到实操、从参数调优到日志定位、从坑点到扩展思路整个捋一遍,希望能让正在用或者准备用的朋友少走点弯路。
1. 先搞清楚Monkey到底在干什么
1.1 一个简单的比喻:请个“猴子”来砸场子
Monkey是Android SDK里自带的一个命令行工具,它的工作方式特别直白:向系统发送伪随机的用户事件流,比如点击、滑动、按键、系统事件,模拟人胡乱操作手机时的场景。你可以把它理解为一只精力旺盛的猴子,坐在你的App面前,完全不按常理出牌,到处乱按乱划。
为什么要这么干?因为真实用户里总有一些“特殊操作流派”——连点、快速滑动、后台切来切去、横竖屏切换、弱网加打断,这些组合场景靠测试人员手工复现是很痛苦的。而Monkey能用极低的成本,在短时间内制造成千上万次随机事件,把App推到各种极限状态。很多低频偶现的崩溃、无响应(ANR)、内存泄漏,往往就是在这种高频随机操作下现出原形。
1.2 Monkey测试的价值边界
不过得先把话说清楚,Monkey不是万能的。它擅长的是“找崩溃”,不是“找Bug”。它不校验业务逻辑对不对,不关心界面显示是否符合预期,它的评价标准就一条:系统在大量随机操作下稳不稳,有没有Crash、ANR、Native Crash,以及CPU、内存有没有异常飙升。
所以它最适合的场景是:
- 版本发版前的稳定性回归测试
- 对已上线的老项目做兼容性摸底
- 测试资源紧张时的冒烟兜底
- 配合Monkey Runner或自研脚本做问题复现
不适合的场景也很明确:涉及业务流程正确性的功能测试、依赖账号状态和数据校验的用例、需要精确断言的UI自动化测试。这些是Appium、Espresso等工具的主场。
1.3 谁应该认真读这篇文章
如果你是个刚接触App自动化测试的新人,能把Monkey命令跑通、看懂日志,就已经超过大多数测试群里“只会跑命令”的同行了。如果你是有一定经验的测试开发,这篇文章里关于参数组合调优、日志分析定位、CI集成和二次开发的思路,应该能帮你把这套工具真正用出生产力。
2. 动手之前,把环境准备和核心原理吃透
2.1 环境准备:其实就一个命令的事
Monkey是Android SDK自带的工具,所以环境要求并不复杂:
- 一台电脑,装好Android SDK Platform-Tools,确保
adb命令可用 - 一台Android设备或模拟器,开启
开发者选项里的USB调试 - 最好准备一台独立的测试机,别拿主力机跑,因为Monkey操作很“暴力”,容易误触到系统设置、卸载应用甚至恢复出厂设置,数据安全第一
检查环境是否OK,终端里执行:
adb devices能看到设备序列号,就说明连接正常。接下来确认Monkey工具路径:
which monkey # 或者在Windows下直接在platform-tools目录中运行Monkey工具的本体其实是个脚本加JAR包组合,其核心是一个monkey.jar,安装在系统分区里。但你不用关心它装在哪,因为adb shell monkey这个命令会自动把参数传给系统里的Monkey框架。
2.2 Monkey的执行机制:事件注入是怎么回事
这里稍微讲点原理,理解了它,后面排查问题会顺很多。Monkey的运行依赖Android系统里的Instrumentation机制和Window Manager。当你在终端敲下adb shell monkey,工具会做这么几件事:
- 解析命令行参数,确定事件序列的生成策略
- 连接活动管理器(ActivityManager),获取当前设备前台App的状态
- 通过系统服务向窗口管理器(WindowManager)注入输入事件
- 循环执行直到事件数量跑满,或者遇到崩溃、ANR等终止条件
事件注入的粒度很细,包含ACTION_DOWN、ACTION_UP、ACTION_MOVE等触摸事件,以及各种KeyEvent按键事件、系统级事件。Monkey自己维护一个事件源,按策略随机选择下一个要发送的事件,发送间隔由--throttle参数控制。
这解释了一个关键问题:为什么Monkey测试不能并行跑多个设备场景?因为事件注入是全局的,它操作的不只是你的App,而是整个设备界面。如果同时开两个Monkey进程,两个猴子互相抢设备焦点,事件注入就会乱套,测试结果自然不可信。所以跑Monkey时,一个设备只跑一个Monkey进程,这是基本原则。
2.3 设备端状态检查清单
正式开跑前,建议做一轮快速检查:
- App处于可测试状态,能用
adb shell am start拉起来,别一启动就崩溃 - 测试账号已登录,基础数据已准备好(Monkey跑起来不会帮你登录)
- 关闭系统自动更新、通知弹窗、锁屏,避免外部干扰影响事件注入
- 确认设备的“保持唤醒”已开启,否则屏幕暗了Monkey的操作会出现大量无效事件
每一条都有实际意义。第4条尤其常见——设备锁屏后,Monkey还在疯狂注入事件,结果注入到锁屏界面上,测试对象压根没被覆盖到,最后测了个寂寞。这种问题还不好定位,因为日志里全是正常的事件记录。
3. Monkey命令与参数,一次讲透
3.1 基础命令结构
adb shell monkey [options] <event-count>event-count是必填参数,表示要发送的随机事件总数。options不需要时可以省略,但实际测试中几乎都会用到。
一个比较典型的完整命令长这样:
adb shell monkey -p com.example.app --throttle 300 -s 12345 -v -v -v --pct-touch 50 --pct-motion 20 --pct-trackball 0 --pct-syskeys 0 --pct-appswitch 5 --pct-anyevent 0 --ignore-crashes --ignore-timeouts --kill-process-after-error 10000这条命令有点长,下面拆开逐个解释。
3.2 关键参数详解与取值逻辑
先把最常用的参数用一张表列出来:
| 参数 | 作用 | 实测建议 |
|---|---|---|
-p <包名> | 指定测试目标包,可多个 | 不写会作用到设备上所有App |
--throttle <毫秒> | 每个事件之间的间隔 | 300~500ms比较稳,太短会导致事件丢失 |
-s <种子值> | 随机数种子,复现用 | 固定种子可复现同一序列 |
-v | 日志级别,最多三个-v | 定位问题建议用三个,日志最详细 |
--pct-touch <百分比> | 触摸事件(点击)占比 | 25~50 |
--pct-motion <百分比> | 滑动事件占比 | 15~25 |
--pct-trackball <百分比> | 轨迹球事件占比 | 手机设备建议0 |
--pct-syskeys <百分比> | 系统按键占比 | 建议0~2,太高会触发Home、音量等 |
--pct-appswitch <百分比> | 启动Activity的占比 | 5~10,模拟用户切换页面 |
--pct-anyevent <百分比> | 其他各类事件 | 0~5 |
--ignore-crashes | 遇到Crash不中断,继续执行 | 大面积扫查时建议开 |
--ignore-timeouts | 遇到ANR不中断,继续执行 | 同上 |
--kill-process-after-error | 出错后杀掉App进程再继续 | 配合ignore系列参数使用 |
百分比这块要重点说一句:所有--pct-*参数的值加起来不需要等于100,Monkey会丢掉未分配的部分,或者按比例归一化处理。但各值最好有明确设计意图,别全堆在一个类型上。
我自己的经验是,常规稳定性测试可以把触摸和滑动事件加起来占70%左右,留一点给系统键和App切换。这样既保证了界面操作强度,又能模拟用户偶尔按Home、切后台场景。但--pct-syskeys真的别给太高,系统按键事件会打断当前Activity,甚至拉到通知栏,事件流会乱掉。
3.3 日志级别与信息提取
-v参数控制日志冗余级别,从低到高分别是:
- 一个
-v:只输出启动信息、事件流基本信息和最终结果 - 两个
-v:增加每个事件注入的Activity切换信息 - 三个
-v:输出最详细的日志,包含每个事件的类型、坐标等细节
但日志细节多,不代表一定要用三个-v。日常快速验证跑一个-v就够,定位Crash时再用三个-v,把输出重定向到文件方便回溯。
adb shell monkey -p com.example.app -v -v -v --throttle 300 -s 12345 10000 > monkey_log.txt 2>&1这里的2>&1很重要。Monkey的日志既有标准输出又有错误输出,两个都重定向到一个文件,才能完整记录整个执行过程。很多人第一次跑完发现日志文件是空的,就是因为去掉错误输出给丢了。
3.4 复现问题离不开的种子值
-s参数是Monkey测试里被低估的宝藏。它指定随机数生成器的初始种子,相同的种子值、相同的事件总数、相同的参数组合,Monkey会生成完全相同的事件序列——也就是说,你遇到一个崩溃,只要把种子值记下来,后续任何时候都能复现同一次“猴子行为”。
排查问题时的标准流程是:
- 发现崩溃,记录当前使用的
-s 值、事件总数和所有参数 - 用相同的命令重新跑一次,看能否复现
- 如果能复现,缩小事件范围,逐步定位是哪一类事件触发的
实操中,有些团队会把“种子值+事件数+参数”做成一条记录,联同Crash日志一起归档到Bug描述里,开发修复时就能精准复现。这比让开发自己盲猜复现路径省力太多。
4. 实操过程:完整跑一次Monkey测试
4.1 第一次快速冒烟跑
假设要测试的App包名是com.example.demo,目标设备已经连接。我先跑一条最基础的命令验证工具链路是否顺畅:
adb shell monkey -p com.example.demo 1000这个命令没有设置种子和事件间隔,Monkey会以最快的速度连续发送1000个事件。跑完终端会显示类似这样的统计信息:
Events injected: 1000 :Dropped: keys=0 pointers=0 trackballs=0 flips=0 ## Network stats: elapsed time=56243ms (0ms mobile, 56243ms wifi) // Monkey finished只要出现Monkey finished,且没有** Monkey aborted due to error,基本说明链路正常。这一步的价值是快速判断App在当前状态下会不会“见光死”——如果连基础事件都扛不住,后面就不用调参了,先修Bug吧。
4.2 一次经典稳定性测试的完整参数设计
快速冒烟通过后,进入正式的稳定性测试。我一般这样设计参数:
adb shell monkey -p com.example.demo \ --throttle 300 \ -s 20250115 \ -v -v -v \ --pct-touch 50 \ --pct-motion 20 \ --pct-pinchzoom 5 \ --pct-trackball 0 \ --pct-syskeys 2 \ --pct-appswitch 8 \ --pct-anyevent 5 \ --ignore-crashes \ --ignore-timeouts \ --kill-process-after-error \ 20000这里的事件总数是20000,--throttle 300让每个事件间隔300毫秒。算一下总时长:20000个事件,每个事件之间平均300ms,再加上事件本身执行和页面加载的时间,整体跑下来大约需要1.5~2小时。这个时长合适——太短测不出问题,太长占着设备也没必要。
参数设计逻辑再讲透一点:
--pct-touch 50:点击类事件占一半,这是所有移动App最频繁的操作手势--pct-motion 20:滑动手势,覆盖列表滚动、轮播图切换--pct-pinchzoom 5:双指缩放,地图、图片类页面常用--pct-trackball 0:手机设备没有轨迹球,直接归零,腾出占比给其他事件--pct-syskeys 2:保留少量系统键,验证Home键切后台后再回来的状态恢复能力--pct-appswitch 8:模拟用户在App之间切换--pct-anyevent 5:兜底事件类型,保证事件源多样性
跑完后,日志尾部同样要出现Monkey finished或者对应的aborted原因。如果跑完没有任何异常,恭喜,至少说明这次版本的稳定性是过关的。
4.3 测试过程中实时观察设备状态
Monkey在跑的时候,同时打开另一个终端窗口观察设备状态,能抓到不少有意思的信息。
CPU和内存:
adb shell top -n 1 | grep com.example.demoLogcat中的崩溃日志:
adb logcat -v time | grep -E "FATAL EXCEPTION|ANR in|am_crash"这两个窗口开着,一旦Monkey执行出错,你能立刻从Logcat看到对应的崩溃栈,从top看到异常占用。这种边跑边观察的方式,比跑完再回头翻日志高效得多。
我自己还习惯在Monkey跑的过程中每隔几分钟截一次图,用adb exec-out screencap -p > screen.png导出来。别小看截图,它记录的是崩溃发生时页面当时长什么样,对开发排查“这个崩溃到底是怎么触发的”特别有用。
4.4 日志结果怎么看
Monkey测试结束后的输出分三部分:
第一部分是事件执行日志。格式类似:
:Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER这里记录每一步在执行什么事件、切换到了哪个Activity。配合-v -v -v的详细输出,可以还原出测试执行的时间线。
第二部分是统计信息。
Events injected: 20000 :Dropped: keys=0 pointers=0 trackballs=0 flips=0这里的Dropped统计丢掉的输入事件数量。如果这个值很大,说明设备处理不过来或者有卡顿——系统已经把事件丢弃了,Monkey是在“空转”,测试的有效性会打折扣。
第三部分是结果判定。
Monkey finished:全部事件执行完毕,没崩溃,测试通过** Monkey aborted due to error:执行过程中遇到崩溃、ANR或超时,被中断// CRASH、// ANR:明文标识的类型
看到Monkey finished还不能掉以轻心,崩溃日志要在Logcat里翻一遍才算数,因为有些Native层崩溃Monkey不会直接终止,需要从Logcat的DEBUG、libc、Fatal signal等关键字里捞出来。
5. 常见问题与排查技巧实录
5.1 必坑指南:我踩过的那些Monkey测试的坑
Monkey测试看着简单,实际操作层面坑是真不少。这里分享几个我踩过且印象深刻的:
坑一:事件都注入到锁屏界面了。设备测试过程中屏幕休眠,Monkey不会自动唤醒屏幕,所有触摸事件全打在锁屏或熄屏上。测试跑完日志里全是正常事件记录,但App实际一个事件都没收到。解决办法简单粗暴:测试全程禁止锁屏,或者用svc power stayon true保持屏幕常亮。
坑二:Monkey把系统设置的弹窗当成了测试目标。比如App崩溃后弹出系统的“应用已停止运行”对话框,Monkey会把崩溃弹窗当成可点击界面继续点,甚至点到“卸载应用”。所以测试机上别放重要数据,权限弹窗尽量提前处理好,该给权限的给权限,该关通知的关通知。
坑三:测试开始没多久就遇到权限弹窗,但权限弹窗不是目标App的Window,Monkey事件全部打在弹窗上,后续所有点击事件都无效。这种问题怎么发现?看日志里--pct-syskeys和弹窗切换记录能瞄出端倪,更直接的办法是跑的时候多截图。我现在的习惯是:测试前把全部运行时权限预先授权,adb shell pm grant逐一处理,然后再跑Monkey。
坑四:误用了旧版SDK的Monkey,很多新参数不支持,命令行直接报错。比如--pct-pinchzoom在旧版中不存在。解决办法是升级Platform-Tools到最新版,并确认设备系统版本不要太老。
5.2 拿到崩溃日志后如何快速定位
Monkey测试的价值在于发现问题,但这还不够,关键是问题的定位效率。拿到一份Crash日志,我一般按下面这个顺序排查:
第一步,看是不是App自身的崩溃。搜索FATAL EXCEPTION,向下找到Process: com.example.demo和Caused by:。这一段基本就是崩溃的根因。
第二步,看崩溃发生的Activity和事件上下文。回到Monkey日志,找到崩溃前的最后几条事件记录,看是在哪个Activity、哪个页面。比如日志显示正在相册页滑动时崩溃,问题大概率出在图片加载或内存处理上。
第三步,结合设备状态判断是不是资源问题。看崩溃时间段top输出里的内存占用,如果接近系统上限,很可能是OOM被系统杀死,这种通常和图片缓存、WebView内存泄漏挂钩。
第四步,用种子值复现并精简。把-s种子固定,事件总数慢慢缩小,比如从20000降到5000、再降到1000,看能不能用更短的事件序列复现同一个崩溃。复现出来之后,连测试参数一起提给开发,开发照着跑就能复现,不用自己绞尽脑汁去构造路径。
ANR问题稍微特殊一点。Monkey日志里出现ANR in com.example.demo,通常还要去/data/anr/目录拉trace文件:
adb shell ls /data/anr/ adb pull /data/anr/traces.txt这个trace里记录了ANR发生时各个线程的调用栈,看主线程卡在什么地方,问题就基本清楚了。
5.3 常见问题速查表
| 现象 | 最可能的原因 | 解决方案 |
|---|---|---|
monkey aborted但没有崩溃栈 | 事件序列变化导致状态异常 | 查看Logcat中临近时间的Activity切换记录 |
测试提前结束,日志显示ANR | 有耗时操作阻塞主线程 | 拉取/data/anr/traces分析调用栈 |
| 测试过程中网络异常 | Wi-Fi不稳定或系统休眠断开 | 使用有线连接或关闭Wi-Fi休眠策略 |
| 事件数跑完了但日志不完整 | 日志被系统缓冲区覆盖 | 增加-v数量,输出重定向到文件 |
| 跑完发现App数据被清空了 | --pct-syskeys触发系统设置操作 | 降低系统键占比,加--pkg-blacklist-file排除系统应用 |
| 崩溃复现不出来 | 种子值或事件参数对不上 | 完整记录命令参数和Logcat时间戳 |
Native层崩溃Fatal signal | 内存越界或so库问题 | 结合tombstone日志分析 |
5.4 对测试结果保持警惕
Monkey测试绿的,不代表App稳定性真的没问题。有一个很容易被忽略的现象:Monkey的事件是随机且不带业务语义的,它很难触发需要特定业务前置条件的页面。比如一个支付流程,需要登录、选择商品、确认订单三步,Monkey大概率不会那么“巧”地把这三步全部操作对。所以Monkey测试覆盖到的是“通用交互稳定性”,业务链路相关的稳定性还要靠自动化测试用例补。
另外我遇到过几次情况:Monkey测试过程中App没崩,但后台日志显示大量内存泄漏。这是因为Monkey的操作节奏偏快,有些泄漏需要长页面停留、多次进出才能累积到崩溃阈值。所以严谨的团队的每次Monkey测试后还要补一轮adb shell dumpsys meminfo分析,对比测试前后同一场景的内存占用变化。
6. 如何把Monkey测试做出工程化价值
6.1 在CI流水线里接入Monkey测试
基础跑法通了之后,接下来值得做的事就是把Monkey测试接到持续集成流水线里,让每次构建都自动跑一轮稳定性冒烟。
一个可参考的流水线设计:
- 构建阶段:打包Debug/Release APK并签名
- 部署阶段:通过adb安装到测试设备集群
- 执行阶段:按预设参数跑Monkey测试,事件总数按每日回归和发版前分级
- 数据采集阶段:自动拉取Logcat、Monkey日志、截图、崩溃日志
- 结果判定阶段:解析日志关键字,生成HTML报告,有Crash/ANR则挂掉Pipeline
- 通知阶段:失败时自动推送消息到IM群
这里最难的一点是设备和CI的集成。设备多了之后,真机管理平台需要维护一套设备分配和清理机制,避免多个任务抢占同一台设备。现在有Sonic云真机、AtxServer2等开源自研平台可以配合,本质上是把Monkey命令封装成平台任务,由平台统一调度和执行。
6.2 基于Monkey日志做二次开发
有人觉得Monkey只能输出日志不能自己控制,其实不然。Monkey的日志格式是固定的,解析起来并不难。你可以写个小脚本,把--pct-*分布、事件注入量、异常类型做成可视化报表。更进一步,Monkey还有--script参数,可以指定一个脚本文件,在随机事件流之外插入特定操作。比如在随机点击的间隙,插入一段“登录”操作,这样即使前置条件复杂,Monkey也能在业务状态下跑圈。
脚本文件的基本格式是:
// 等待2秒 type= wait duration= 2000 // 点击坐标(100,200) type= tap x= 100 y= 200 // 启动指定Activity type= launchActivity activity= com.example.demo/.MainActivity这种脚本用法适合做“登录后无人值守的随机稳定性测试”,比纯随机事件又多了一层业务覆盖。实际使用中需要配合--script file.txt参数传入脚本。
6.3 与其他稳定性测试工具的搭配思路
Monkey不是唯一的选择,但它是性价比最高的入门方案。和市面其他工具放一起对比,它的优劣势很清楚:
| 工具 | 核心能力 | 优势 | 劣势 |
|---|---|---|---|
| Monkey | 随机事件注入 | 系统自带、零依赖、上手快 | 无业务断言、随机不可控 |
| Maxim | 智能遍历+Monkey扩展 | 可配置事件策略、更智能的点击 | 需要额外安装、维护成本 |
| AppCrawler | 自动遍历爬取 | 基于页面遍历的深度测试 | 对复杂页面适配有要求 |
| 自研脚本 | 定制事件流 | 完全可控 | 开发成本高 |
实际工作里,我的组合拳一般是:优先用Monkey做常规随机稳定性测试,配合每次发版;再用Maxim或AppCrawler做版本重点功能的深度遍历,在发版前补一轮。两者覆盖维度不同——Monkey覆盖的是“无序操作下的系统稳定性”,遍历工具覆盖的是“功能路径可达的完整性”。
6.4 把Monkey测试结果变成团队能看懂的结论
最后想聊一个容易被忽视的点:测试结果输出。很多团队跑完Monkey,把日志扔到群里就完事了,其他人根本看不懂。我的经验是每次跑完输出一份简版报告,内容包含:
- 测试环境:设备型号、Android版本、App版本
- 测试参数:种子值、事件总数、事件类型占比
- 结果统计:总事件数、实际注入数、丢弃数
- 异常清单:崩溃类型、发生的Activity、疑似原因分析
- 复现步骤:种子值、最小化事件序列
这份报告用固定模板生成,统一存档。这样不仅开发能快速定位问题,连续几个版本跑下来,还能对比出稳定性趋势——比如某个版本的Crash率从3%涨到10%,那说明这个版本引入了明显的稳定性回归,测试同学在评审会上就有据可依了。
7. 关于Monkey测试,我最后想说的话
Monkey测试这个东西,工具本身很简单,但真正能把它用出价值的人,往往都是那些肯在日志分析、参数设计和流程规范上花功夫的测试工程师。我见过太多人把Monkey当成“跑一跑交差”的任务,写完报告就完事,结果稳定性的坑留到线上被用户踩。
我个人实际工作里,最受益的一个习惯是:每次Monkey测试前,先把测试目标和预期结果写清楚——是验证新版本没有崩溃?还是排查某个历史Bug是否修复?还是摸底某个新引入的三方SDK稳定性?目标不同,参数设计的侧重点就完全不同。带着目标去跑,跑完后才有底气说“这个版本稳定性状态如何”。
如果你所在的项目还没有把Monkey测试纳入日常回归体系,建议从下一次构建开始,加一条最简单的任务:指定包名、固定种子、跑5000个事件,输出一份日志存档。坚持几个版本之后,你再回头看,会发现这项工作早就不只是“让猴子乱按”那么简单了。