移动端 App 测试要点¶
移动端测试和 Web 最大的区别是:你的进程随时可能被杀,你的权限随时可能被收回,你的网络随时会掉。 这些不是异常场景,是日常场景。
下面的命令在 Pixel 7(Android 14)、iPhone 15(iOS 17.2)、adb 35.0.2、Xcode 15.2 上验证过;未特别说明时包名为 com.qalab.app。
一、安装、升级与降级¶
安装矩阵¶
| 场景 | Android 命令 | iOS 对应 | 必查项 |
|---|---|---|---|
| 首次安装 | adb install app-release.apk |
TestFlight / xcrun devicectl |
冷启动耗时、首屏、权限弹窗时机 |
| 覆盖安装 | adb install -r app-release.apk |
同 Bundle ID 覆盖 | 登录态、本地数据、桌面快捷方式 |
| 保留数据卸载 | adb shell pm uninstall -k com.qalab.app |
无对应能力 | -k 保留 /data/data 与外部存储 |
| 彻底卸载重装 | adb uninstall com.qalab.app |
长按删除 App | 残留文件、Keychain、SD 卡目录 |
| 降级安装 | adb install -d -r app-old.apk |
无法降级,只能删后重装 | 数据库 schema、缓存格式 |
| 跨大版本升级 | 装 v1.8 后用 v2.3 覆盖 | App Store 升级 | 迁移脚本、埋点版本号 |
| 存储不足 | 填满磁盘后安装 | 同 | 失败提示是否可理解 |
真实安装失败码,看到就要能定位:
INSTALL_FAILED_VERSION_DOWNGRADE # 没加 -d,或 versionCode 低于已安装版本
INSTALL_FAILED_UPDATE_INCOMPATIBLE # 签名不一致,测试包与正式包混装
INSTALL_FAILED_INSUFFICIENT_STORAGE # 空间不足
INSTALL_PARSE_FAILED_NO_CERTIFICATES # APK 未签名或被二次篡改
INSTALL_FAILED_TEST_ONLY # AndroidManifest 里 testOnly="true"
testOnly 这一条尤其常见:Android Studio 直接 Run 出来的包带 android:testOnly="true",用 adb install 装到另一台机器就报 INSTALL_FAILED_TEST_ONLY。要么加 adb install -t,要么用 assembleRelease 出包。
# 查版本与签名,确认装的是哪个包
adb shell dumpsys package com.qalab.app | grep -E 'versionName|versionCode|firstInstallTime|lastUpdateTime'
adb shell pm list packages -3 | grep qalab
apksigner verify --print-certs app-release.apk
升级要做的事¶
升级测试不是“能打开就行”,要逐项确认数据迁移:
- 登录态是否保留(token 存储 key 有没有改名)。
- 本地数据库是否迁移(Room 的
Migration、iOS 的 CoreData 轻量迁移)。 - 本地缓存里的旧结构能否被新代码解析。
- 上次未提交的草稿、购物车、离线队列是否还在。
- 旧版本遗留的权限、通知渠道是否仍然有效。
- 桌面小组件、快捷方式、通知点击是否还能拉起。
案例 1:降级安装后启动即崩。 测试为了复现旧版问题,用 adb install -d -r 把 v2.3 降到 v1.8,启动直接崩溃:
java.lang.IllegalStateException: Room cannot verify the data integrity.
Looks like you've changed schema but forgot to update the version number.
根因是 v2.3 给表加了列并把 user_version 提到 12,v1.8 的 Room 只认 11,检测到 schema hash 不匹配就自杀。这不是“降级”本身的问题,而是降级后仍读着新版数据库。正确做法是降级前 adb shell pm clear com.qalab.app,或在 Application 启动时捕获该异常并重建本地库(同时上报,避免静默丢数据)。
二、权限¶
Android 运行时权限的三个状态¶
| 状态 | checkSelfPermission |
shouldShowRequestPermissionRationale |
再次申请会弹窗 |
|---|---|---|---|
| 未申请 | DENIED | false | 是 |
| 拒绝过一次 | DENIED | true | 是 |
| 永久拒绝 | DENIED | false | 否,直接回调拒绝 |
Android 11 起,用户连续拒绝两次即被视为“不再询问”,系统不再弹窗。所以“首次拒绝后再申请”的用例必须写成两条:拒绝一次后申请(应弹窗 + 应显示 rationale)、拒绝两次后申请(不弹窗,应引导去设置)。
// 判定是否该展示“去设置开启”引导
fun shouldGuideToSettings(activity: Activity, perm: String): Boolean =
!ActivityCompat.shouldShowRequestPermissionRationale(activity, perm) &&
ContextCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED
注意:首次安装、从未申请过时 shouldShowRequestPermissionRationale 同样返回 false,不能据此判断“永久拒绝”。必须先记录“是否已申请过”,再结合返回值判断。
# 查看与修改权限状态(调试用)
adb shell dumpsys package com.qalab.app | grep -A 20 'runtime permissions'
adb shell pm grant com.qalab.app android.permission.CAMERA
adb shell pm revoke com.qalab.app android.permission.CAMERA
adb shell appops set com.qalab.app CAMERA deny # 模拟“拒绝但不再询问”
iOS 权限的特殊性¶
iOS 的权限弹窗每个 App 每次安装只弹一次。用户点了“不允许”,代码再调 requestAuthorization 不会弹窗,直接回调 denied。所以“二次申请”在 iOS 上不是再弹一次,而是必须把用户送到“设置 → 隐私与安全性 → 相机”。
AVCaptureDevice.requestAccess(for: .video) { granted in
if !granted {
// 无法再次弹窗,只能引导
DispatchQueue.main.async { self.showSettingsAlert() }
}
}
# 模拟器上重置/预置权限,真机不支持
xcrun simctl privacy booted reset all com.qalab.app
xcrun simctl privacy booted grant photos com.qalab.app
xcrun simctl privacy booted revoke camera com.qalab.app
通知权限更特殊:调 requestAuthorization 会弹窗;但用 UNAuthorizationOptions.provisional(临时授权)不弹窗,通知静默进通知中心,用户“保留”后才转为正式授权。测试要覆盖三条路径:允许、拒绝、临时授权后升级。
案例 2:永久拒绝后点“拍照”无任何反应。 用户在 Android 13 上拒绝了两次相机权限,之后每次点“拍照”按钮都没有 toast、没有跳转,日志里只有一行 onRequestPermissionsResult: DENIED。产品预期是“引导去设置”。根因是回调里只处理了 GRANTED,DENIED 分支空实现。修复时同时补了 shouldShowRequestPermissionRationale 判断,把“首次拒绝”和“永久拒绝”分成两套文案,并且设置页跳转要用 Settings.ACTION_APPLICATION_DETAILS_SETTINGS 带上包名,否则会落到应用列表。
三、生命周期:前后台、锁屏、杀进程¶
| 操作 | 命令 | 关注点 |
|---|---|---|
| 退到后台 | adb shell input keyevent KEYCODE_HOME |
onStop 是否停掉定时器、传感器、播放 |
| 回到前台 | adb shell am start -n com.qalab.app/.MainActivity |
数据是否刷新、是否重复弹权限 |
| 锁屏 | adb shell input keyevent KEYCODE_POWER |
音视频是否暂停、计时是否继续 |
| 杀后台进程 | adb shell am kill com.qalab.app |
只杀后台,模拟系统回收 |
| 强制停止 | adb shell am force-stop com.qalab.app |
等价于用户在设置里“强行停止” |
| 内存紧张 | adb shell am send-trim-memory com.qalab.app RUNNING_LOW |
onTrimMemory 是否释放图片缓存 |
am kill 与 force-stop 的差别很容易混淆:am kill 只能杀“可被杀”的后台进程,不改变 App 的停止状态;force-stop 会把包标记为 stopped,之后广播、闹钟、推送都收不到,直到用户再次手动启动。
进程死亡恢复¶
Android 在后台内存不足时会直接杀进程。回到前台时系统重建 Activity,并回放 onSaveInstanceState 的 Bundle;如果没存,savedInstanceState == null。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 进程死亡重建时这里为 null,不能假设用户是首次进入
val restored = savedInstanceState?.getString("draft")
if (restored == null && isComingFromRecents()) showDraftLostTip()
}
iOS 对应的是 UIApplicationDelegate 的 state restoration 或 SwiftUI 的 @SceneStorage。没做恢复的页面,用户切回来看到的是空列表 + 空搜索框,比崩溃更让人困惑。
案例 3:从后台回来变成白屏。 某列表页把数据存在 ViewModel 里,进程被回收后重建,ViewModel 重新构造、请求还没回来,页面渲染了一个 null 的 adapter,白屏 2 秒。根因是 onCreate 里默认 savedInstanceState == null 就是首次启动,跳过 loading 态。修复是显式区分“进程死亡恢复”和“首次启动”,恢复时先渲染骨架屏并重新拉取。
另外要测后台停留时长:Android 的 Doze 会在息屏一段时间后限制网络与闹钟,iOS 会挂起 App。后台 30 分钟再回来,检查: - 登录 token 是否已过期,是否有静默续期。 - 列表是否还是 30 分钟前的数据。 - 上传/下载任务是否被中断,恢复后是否续传。 - 推送角标与实际未读数是否一致。
四、弱网与网络切换¶
三种限速方式¶
Android 模拟器(AVD)自带网络整形:
adb emu network status # 查看当前延迟与速率
adb emu network delay 3000 # 3s 延迟
adb emu network speed gsm # 上行/下行约 40/14 kbps 级别
adb emu network speed full # 恢复
启动时也可以直接指定:emulator -avd Pixel_7_API_34 -netdelay gsm -netspeed gsm。
iOS 用 Network Link Conditioner(需装 Additional Tools for Xcode),内置 3G、Edge、LTE、DNS 慢、100% 丢包等配置,作用于整机,比在 App 里改超时更接近真实。
Charles 的 Throttle 适合精确定位某个接口:Proxy → Throttle Settings → 勾选 Enable Throttling,预设 3G(带宽约 780 kbps、延迟 100 ms、利用率 50%),也可以只对 api.example.com 生效。配合 Breakpoints 可以让指定请求永不返回,验证超时分支。
判定标准¶
弱网不是“看着能转圈就行”,要有明确阈值:
| 场景 | 预期行为 | 判定标准 |
|---|---|---|
| 3s 延迟 | 显示 loading,可取消 | 3s 内不出现“网络错误” |
| 请求超时 | 超时时间 ≤ 15s,有重试入口 | OkHttp 默认 10s connect / 10s read |
| 断网 | 立即提示,走本地缓存 | 有网后自动恢复,不需重启 |
| 丢包 30% | 重试有退避,不放大流量 | 重试间隔指数增长,最多 3 次 |
| Wi-Fi ↔ 4G | 请求不中断或快速失败重连 | 不出现半小时无响应 |
真实报错长这样,看到要能对应到配置项:
java.net.SocketTimeoutException: failed to connect to /10.0.2.2 (port 8080) after 10000ms
java.io.IOException: canceled due to timeout
javax.net.ssl.SSLHandshakeException: Connection closed by peer
第三条在弱网 + 代理场景很常见:Charles 证书没装到系统信任区,或 TLS 版本被中间设备掐断,不要一律当“网络差”处理。
案例 4:弱网重试造成重复下单。 下单接口 10s 超时,客户端失败后自动重试 3 次,用户在 3G 下点了两次,最终生成 4 笔订单。根因是重试没有幂等键,服务端也没做去重。修复是客户端生成 requestId 放进 header,服务端按 requestId 唯一索引,重复请求返回首次结果。验证时用 Charles 把响应延迟到 12s,同时在客户端断开,观察只落一单。
五、中断场景¶
| 中断 | 模拟命令 | 检查点 |
|---|---|---|
| 来电 | adb emu gsm call 13800138000 |
音视频是否暂停、页面是否被遮挡、挂断后是否恢复 |
| 短信 | adb emu sms send 10086 "test" |
通知展示、点击跳转 |
| 耳机插入/拔出 | 手动,或 adb shell input keyevent 79 |
音频路由切换,拔出后是否外放 |
| 低电量 | adb shell dumpsys battery set level 5 |
是否触发省电策略、是否限制后台 |
| 充电 | adb shell dumpsys battery set ac 1 |
充电提示、动画 |
| 闹钟/日历提醒 | 系统时钟设置 | 全屏 Intent 是否打断 |
| 分屏/画中画 | 手动拖拽 | 布局是否重排、视频是否续播 |
| 系统弹窗 | 权限、低存储、系统更新 | 回来后页面状态 |
adb emu gsm call 13800138000
adb emu gsm cancel 13800138000
adb emu sms send 10086 "hello"
adb shell dumpsys battery set level 5
adb shell dumpsys battery reset
低电量测试经常被忽略的一点:Android 的省电模式和 iOS 的低电量模式都会限制后台刷新与自动播放。iOS 低电量模式下视频自动播放会直接被拒,报 NotAllowedError,这不是 bug,但产品文案必须能解释。
六、深链与推送¶
深链¶
# Android App Links / 自定义 scheme
adb shell am start -W -a android.intent.action.VIEW \
-d "https://example.com/detail/88" com.qalab.app
adb shell am start -W -a android.intent.action.VIEW \
-d "qalab://detail?id=88" com.qalab.app
# 查看 App Links 验证状态(Android 12+)
adb shell pm get-app-links com.qalab.app
adb shell pm set-app-links --package com.qalab.app 1 all
深链要测的组合:
- App 未安装 → 落地页/H5 兜底,不能白屏。
- App 已安装未启动 → 冷启动,参数从
getIntent()/application(_:open:options:)进入。 - App 在后台 → 热启动,走
onNewIntent,此时 Activity 不会重建,如果只在onCreate解析参数就会丢。 - 已登录 / 未登录 → 未登录先跳登录,登录后必须回到原目标页(参数要暂存)。
- 参数非法/缺失 → 降级到首页,不崩溃。
- 重复点击同一链接 → 不叠加多个页面实例。
Android 12 起 App Links 默认不再自动打开,必须验证通过(assetlinks.json 可访问、签名指纹匹配)或用户手动允许,否则链接会落到浏览器。这是“测试环境好好的,线上全进浏览器”的典型原因。
curl -s https://example.com/.well-known/assetlinks.json | jq .
curl -s https://example.com/.well-known/apple-app-site-association | jq .
推送¶
| 检查项 | Android | iOS |
|---|---|---|
| 权限 | Android 13+ 需 POST_NOTIFICATIONS 运行时权限 |
首次启动请求,或 provisional |
| 通道 | Android 8+ 必须建 NotificationChannel | 无通道概念,有中断级别 |
| 离线 | 厂商通道(小米/华为/OPPO/vivo) | APNs |
| 点击 | PendingIntent 携带参数,冷启动要能取到 |
didReceive response 取 userInfo |
| 角标 | 需 Launcher 支持 | applicationIconBadgeNumber |
# 查看通知是否真的发出、通道是否被关闭
adb shell dumpsys notification --noredact | grep -A 8 com.qalab.app
# 勿扰模式状态:0=关闭 1=仅优先 2=完全静音 3=仅闹钟
adb shell settings get global zen_mode
要覆盖的推送场景:前台收到(是否展示横幅)、后台收到、杀进程后收到、通知权限关闭、通道被用户关闭、点击后冷启动、点击后热启动、多条聚合、免打扰模式。厂商通道的离线推送必须在真机上测,模拟器只走 FCM,测不出国内 ROM 的保活与限制。
七、本地存储与清缓存¶
| 位置 | 内容 | 清除方式 |
|---|---|---|
Android shared_prefs/ |
登录 token、设置项 | 清除数据 / pm clear |
Android databases/ |
Room/SQLite | 清除数据 |
Android cacheDir |
图片、H5 离线包 | 应用内“清除缓存” |
| Android 外部私有目录 | 下载文件、日志 | 卸载时删除(getExternalFilesDir) |
iOS Library/Caches |
图片、WebView 缓存 | 系统“清除缓存”或应用内 |
iOS Documents |
用户数据、离线文件 | 删除 App |
| iOS Keychain | token、设备标识 | 卸载不删除 |
案例 5:iOS 卸载重装后自动登录到别人的账号。 测试机上 A 账号登录后卸载 App,重装打开直接进了 A 的首页,而此时输入框里是 B 的手机号。根因是 token 存在 Keychain,iOS 卸载 App 不会清 Keychain,新安装读到了旧 token,服务端也认。修复是启动时校验“Keychain token 的 userId”与“当前设备绑定信息”,并在首次启动写入安装标识,不一致就强制登出。这个坑在共享测试机上极易造成“数据串号”,且很难复现。
# Android 查看应用私有数据(debuggable 包)
adb shell run-as com.qalab.app ls -R /data/data/com.qalab.app
adb shell run-as com.qalab.app cat shared_prefs/config.xml
# 清数据 vs 清缓存
adb shell pm clear com.qalab.app # 全清,等价于重装
adb shell rm -rf /data/data/com.qalab.app/cache # 仅清缓存(需 root/run-as)
“清除缓存”按钮的验收标准:清完后登录态仍在、首页能打开、图片重新下载;如果清完要求重新登录,说明缓存和凭证没分开存,属于实现缺陷。
八、耗电、内存与流量¶
耗电¶
adb shell dumpsys batterystats --reset
# 执行 30 分钟典型场景(后台待机 + 前台浏览 + 推送)
adb shell dumpsys batterystats > batterystats.txt
adb shell dumpsys batterystats --charged com.qalab.app > charge.txt
把 batterystats.txt 丢进 Battery Historian(docker run -p 9999:9999 gcr.io/android-battery-historian/stable:3.0 --port 9999)可以看到唤醒锁、GPS、网络、JobScheduler 的时间线。重点看三件事:是否有长持有 WakeLock、是否有高频定位、息屏后是否有周期性唤醒。
iOS 用 Xcode 的 Energy Log,或设置 → 电池 → 按 App 查看“后台活动”。后台 8 小时耗电超过 5% 就要查后台任务与定位。
内存¶
关注 TOTAL PSS。反复进入退出同一页面 10 次,PSS 持续上涨且不回落,基本就是泄漏。iOS 用 Instruments 的 Allocations + Leaks,或 Xcode Debug Memory Graph 看循环引用。
流量¶
adb shell dumpsys netstats detail | grep -i uid
adb shell dumpsys package com.qalab.app | grep -i uid
更直观的方式是 Charles 统计单次会话流量,或用 Android Studio Network Profiler。判定标准要写死:首页冷启动流量 ≤ 500KB、列表翻页 ≤ 200KB/页、图片默认走 WebP、视频只在 Wi-Fi 预加载。
九、机型适配¶
| 维度 | 典型问题 | 验证方式 |
|---|---|---|
| 屏幕密度 | 1px 边框在 3x 下消失或过粗 | 用 dp 而非 px,真机看截图 |
| 刘海/挖孔 | 顶部内容被遮挡 | WindowInsets / safeAreaInsets |
| 折叠屏 | 展开后布局拉伸、重启 Activity | android:resizeableActivity="true" |
| 平板/大屏 | 列表一列拉满 1200dp | sw600dp 布局,限制内容最大宽度 |
| 低端机 | 动画掉帧、启动 5s+ | 2GB 内存机型实测 |
| 厂商 ROM | 后台被杀、权限被二次管控 | 小米/华为/OPPO 真机各一台 |
| 字体缩放 | 系统字体调大后文字截断 | 设置最大字号,检查按钮与列表 |
| 深色模式 | 硬编码白色背景刺眼 | 切换系统深色模式逐页看 |
厂商 ROM 的额外权限要单独测:小米的“自启动”、华为的“应用启动管理”、OPPO 的“允许后台运行”。这些不在标准权限模型里,用户关了之后推送收不到、后台任务不执行,但 App 内没有任何提示。
十、iOS 与 Android 的关键差异¶
| 维度 | Android | iOS | 测试影响 |
|---|---|---|---|
| 返回 | 系统返回键/手势 | 无全局返回,靠导航栏 | Android 要测物理返回是否退出页面 |
| 权限 | 可反复申请,二次即永久拒绝 | 每次安装只弹一次 | 引导文案与跳转逻辑不同 |
| 后台 | 进程可长期存活,受 Doze 限制 | 进入后台很快被挂起 | 后台任务、上传、定位策略不同 |
| 推送 | FCM + 厂商通道 | 仅 APNs | 国内 Android 离线到达率要单独验 |
| 文件 | 可直接读写外部存储 | 沙盒 + Keychain 跨卸载保留 | 清缓存/重装行为不同 |
| 剪贴板 | 读取无提示(Android 12+ 有 toast) | iOS 16+ 读取弹“允许粘贴” | 自动化脚本会被弹窗打断 |
| 追踪 | 无 ATT | iOS 14.5+ ATT 弹窗 | 广告归因、IDFA 获取 |
| 日期格式 | 跟随 locale | 跟随 locale,且解析更严格 | 跨端展示不一致 |
十一、隐私合规¶
监管检查的不是“有没有隐私政策”,而是收集的时机、范围、告知是否一致。测试要按下面的顺序逐项过:
- 首次启动是否在任何 SDK 初始化之前弹出隐私政策,用户同意后才初始化统计、推送、广告 SDK。
- 拒绝隐私政策后,App 是否仍可浏览基础内容,且不产生任何网络采集请求(用 Charles 抓包验证)。
- 申请的权限是否与当前功能匹配:不点拍照就不该申请相机,不进地图就不该申请定位。
- 是否存在超范围收集:IMEI、IMSI、MAC、Android ID、应用列表、剪贴板。
- 隐私政策里列出的第三方 SDK,是否与实际打包的 SDK 一致(
./gradlew dependencies或解包看libs/)。 - 是否提供账号注销、个人信息导出/删除入口,且真的生效。
- 定向推送是否有明显关闭入口。
- 未成年人模式、实名认证是否符合应用商店要求。
# 看实际打进去的 SDK 与权限
adb shell dumpsys package com.qalab.app | grep -E 'requested permissions|declared permissions'
./gradlew :app:dependencies --configuration releaseRuntimeClasspath | grep -i -E 'umeng|jpush|bugly'
常见违规点:把 READ_PHONE_STATE 写进 Manifest 却从不使用;隐私弹窗点“拒绝”后仍初始化推送 SDK;SDK 在 Application.onCreate 里自启动并读取设备信息。这些在应用商店审核和监管抽检中都是硬伤。
十二、崩溃取证¶
Android¶
# 实时看崩溃(只看 crash buffer,避免被业务日志淹没)
adb logcat -b crash -d
adb logcat -b crash -v threadtime
# 系统 dropbox 里的崩溃记录
adb shell dumpsys dropbox --print data_app_crash
# native 崩溃
adb shell ls /data/tombstones/
adb shell cat /data/tombstones/tombstone_00
# ANR
adb shell ls /data/anr/
adb shell cat /data/anr/traces.txt
Java 崩溃栈直接可读;native 崩溃要符号化:
ndk-stack -sym app/build/intermediates/merged_native_libs/release/out/lib/arm64-v8a \
-dump crash.txt
Release 包的 Java 堆栈是混淆的,需要 mapping 文件:
iOS¶
# 找到崩溃报告
ls ~/Library/Logs/DiagnosticReports/*.ips
# 用 dSYM 符号化单个地址
atos -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
-arch arm64 -l 0x104b40000 0x104b4a2c0
# 确认 dSYM 与二进制 UUID 匹配
dwarfdump --uuid MyApp.app/MyApp
dwarfdump --uuid MyApp.app.dSYM
# 旧脚本仍可用,Xcode 15 起不推荐(对 .ips 支持差)
./symbolicatecrash MyApp.crash MyApp.app.dSYM > symbolicated.crash
现在 Xcode 15 导出的崩溃是 .ips JSON 格式,symbolicatecrash 经常解析失败,优先用 Xcode → Window → Organizer → Crashes,或直接把 .ips 与 dSYM 一起上传到崩溃平台。手工符号化只作为最后手段。
UUID 不匹配是最常见的“符号化失败”原因:CI 每次构建都会生成新 UUID,dSYM 没归档就永久丢失。上线包必须把 dSYM 上传到崩溃平台,并在构建流水线里校验。
案例 6:崩溃率 0.3% 但无法定位。 线上崩溃平台只显示 SIGSEGV 和一堆十六进制地址。查下来是 CI 用 strip 去掉了符号且未保留 dSYM,只能等下一版复现。之后在流水线里加了 dwarfdump --uuid 与 dSYM 上传校验,并把 DEBUG_INFORMATION_FORMAT 设为 DWARF with dSYM File。
十三、Monkey 稳定性测试¶
# 基础随机事件流
adb shell monkey -p com.qalab.app -v 500
# 可控节奏 + 事件配比 + 固定种子(可复现)
adb shell monkey -p com.qalab.app \
--throttle 300 \
--pct-touch 50 --pct-motion 20 --pct-nav 10 --pct-majornav 5 \
--ignore-crashes --ignore-timeouts --monitor-native-crashes \
-s 20261022 -v -v 5000
| 参数 | 作用 | 建议值 |
|---|---|---|
--throttle |
事件间隔 ms | 300,太快会把人造 ANR 当缺陷 |
-s |
随机种子 | 固定值,便于复现 |
--pct-touch |
点击占比 | 50 |
--ignore-crashes |
崩溃后继续 | 压测时开启 |
--ignore-timeouts |
ANR 后继续 | 压测时开启 |
--monitor-native-crashes |
监控 native 崩溃 | 必开 |
--pkg-whitelist |
只测指定包 | 等价 -p |
跑完要检查三件事:/data/anr/ 是否新增、/data/tombstones/ 是否新增、dumpsys dropbox 是否有 data_app_crash。Monkey 只覆盖“随机点击不崩”,不覆盖业务正确性,别拿它当功能测试。
十四、验收清单¶
| 类别 | 验收项 | 判定 |
|---|---|---|
| 安装 | 首次/覆盖/降级/空间不足 | 均有明确结果,无静默失败 |
| 升级 | 登录态、数据库、草稿、缓存 | 数据无丢失,迁移可回滚 |
| 权限 | 允许/拒绝一次/永久拒绝/设置页返回 | 每条路径都有下一步 |
| 生命周期 | 后台 30 分钟、锁屏、杀进程、恢复 | 不白屏、不重复提交、状态可恢复 |
| 弱网 | 3s 延迟、断网、丢包、切换网络 | 有 loading、有超时、有重试 |
| 中断 | 来电、短信、耳机、低电量 | 中断可恢复,音频路由正确 |
| 深链 | 未安装/冷启/热启/未登录/非法参数 | 均落到正确页面 |
| 推送 | 前台/后台/杀进程/权限关闭/点击 | 到达、跳转、角标一致 |
| 存储 | 清缓存、清数据、卸载重装 | 凭证与缓存分离,无串号 |
| 性能 | 冷启动 ≤2s、PSS 无持续上涨、流量达标 | 有基线数据对比 |
| 机型 | 低端机、折叠屏、刘海屏、大字号 | 无遮挡、无截断 |
| 合规 | 隐私弹窗时机、权限最小化、SDK 清单 | 拒绝后无采集 |
| 稳定性 | Monkey 5000 事件、崩溃可符号化 | 无新增 crash/ANR |