移动端自动化:测试中最常用的 ADB 命令应该怎么组织成可复用能力

上一篇我讲了,为什么 Android 测试值得先把 ADB 这层基础能力打好。这一篇继续往下走,不再讨论“该不该做”,而是直接讨论一个更现实的问题:

测试里最常用的 ADB 命令,到底应该怎么组织,才能真的变成可复用能力,而不是一堆散乱脚本。

一开始接触 ADB,容易走两个极端:

  • 一种是把 ADB 当命令手册,谁要用就自己搜命令
  • 另一种是过早封装成很重的平台,但底层动作根本没梳理清楚

这两个方向最后都会出问题。

前者的问题是复用差、排障慢、不同人执行方式不一致;后者的问题是平台表面很完整,但一旦设备异常、命令超时、证据缺失,底层没有稳定能力支撑,平台只会把问题藏得更深。

所以更推荐的方式是:先把高频 ADB 动作按测试任务拆出来,形成一层稳定的“设备操作能力层”,再让脚本、流水线、平台去调用它。

一、不要按命令记忆 ADB,要按测试任务组织 ADB

如果只是从文档角度看,ADB 命令很多,但真实测试里高频出现的能力并没有那么分散。大多数时候,你真正反复用到的是下面几类任务:

  • 设备发现与连通性检查
  • 应用安装、卸载、清缓存、拉起
  • 页面外输入与系统级控制
  • 日志采集与证据留存
  • 文件推送与结果回收
  • 异常后的设备恢复

这几个任务看起来简单,但它们几乎覆盖了移动端自动化里最核心的“外围动作”。无论你后面接的是 Appium、自研框架、冒烟脚本,还是设备池调度,这些动作都绕不过去。

所以更稳妥的建议一直是:

不要让团队成员记一堆零散命令,而要让体系里沉淀出一套统一任务:

  • check_device
  • install_app
  • reset_app
  • launch_app
  • collect_logs
  • capture_screen
  • recover_device

这样以后你换实现、换脚本语言、换执行平台,使用方都不用重新理解底层细节。

二、设备发现与连通性检查,是所有 ADB 自动化的第一层

写 ADB 脚本时,如果上来第一步就是安装 APK 或启动应用,这个顺序其实很危险。因为只要设备状态不稳定,后面的任何操作都有可能变成偶发失败。

所以更稳的做法通常是把设备检查做成统一前置任务。

最基础的命令是:

1
2
3
4
5
adb devices -l
adb -s <serial> get-state
adb -s <serial> shell getprop ro.product.model
adb -s <serial> shell getprop ro.build.version.release
adb -s <serial> shell getprop ro.build.version.sdk

这些命令看着普通,但在工程里有几个关键价值。

1. 先确认设备是不是 device,不要直接执行业务动作

你在 adb devices 里常见到的几种状态:

  • device
  • offline
  • unauthorized
  • 根本查不到设备

这四种状态的处理完全不同。如果这一步不先分流,后面会出现大量误判。

比如:

  • offline 往往是连接层问题或 ADB server 异常
  • unauthorized 本质上是授权弹框没处理
  • 查不到设备可能是 USB、驱动、端口映射、设备重启导致

2. 设备元信息要提前采集,不要等失败后再补证据

通常会在任务开始时就把这些信息采集下来:

  • 序列号
  • 机型
  • Android 版本
  • SDK 版本
  • 电量
  • 剩余存储空间

因为同一个用例在不同机型、不同系统版本上的表现很可能不同。如果不保留这些上下文,后面复盘时会很被动。

例如电量过低时,某些系统会自动限制后台行为;存储空间过低时,安装和截图都可能失败。这些都不是“用例逻辑错误”,但如果没有设备上下文,看起来就像随机故障。

3. 检查动作最好有超时和重试,不要让流水线无限卡住

ADB 的一个常见问题是命令本身未必报错,但会卡很久。尤其是:

  • 设备刚重启
  • USB 连接不稳定
  • 无线调试链路抖动
  • 设备被系统弹框阻塞

所以连通性检查必须有:

  • 单次命令超时
  • 限次重试
  • 最终明确失败原因

否则 CI 上最容易出现的不是“直接失败”,而是一个任务长时间挂死。

三、安装、卸载、清缓存、拉起应用,是最应该标准化的动作

这是移动端测试里最常见的一组操作,也是最容易被写乱的一组操作。

常见命令包括:

1
2
3
4
5
adb -s <serial> install -r app.apk
adb -s <serial> uninstall com.example.app
adb -s <serial> shell pm clear com.example.app
adb -s <serial> shell am start -n com.example.app/.MainActivity
adb -s <serial> shell monkey -p com.example.app -c android.intent.category.LAUNCHER 1

这些命令如果放到真实项目里,不适合只当成临时操作,至少应该沉淀成下面几种标准任务。

1. install_app

这个任务最好不只是单纯执行 adb install,而是至少带上这些能力:

  • 判断 APK 路径是否存在
  • 安装前确认设备可用
  • 识别安装返回结果
  • 区分覆盖安装、首次安装、安装失败
  • 在失败时保留错误输出

因为 ADB 安装失败的原因很多,比如:

  • INSTALL_FAILED_VERSION_DOWNGRADE
  • 存储空间不足
  • 签名不一致
  • 包损坏
  • 设备离线

如果脚本只返回一个“安装失败”,后续几乎没法排查。

2. reset_app

这类任务通常比卸载重装更高频。大量测试场景只需要把应用状态重置,不需要重新安装。

常见动作是:

1
2
adb -s <serial> shell pm clear com.example.app
adb -s <serial> shell am force-stop com.example.app

这个任务的价值在于:

  • 成本比重装低
  • 速度更快
  • 适合回归和冒烟前清状态

但要注意一个边界:pm clear 清掉的是应用数据,不会处理系统权限弹窗、系统缓存异常、测试账号风控状态等问题。把“清数据”直接等同于“完全初始化”,这个判断并不成立。

3. launch_app

启动应用看上去简单,实际上是非常适合统一封装的动作。因为不同场景下启动方式不一样:

  • 指定 Activity
  • 根据包名走 monkey
  • 启动后等待首屏稳定
  • 启动失败后自动采证

更倾向把“启动成功”的定义也明确下来,比如:

  • 应用进程已存在
  • 指定页面 Activity 已进入前台
  • 启动后若干秒内没有闪退

否则很多脚本只是把命令发出去了,但并没有真正确认应用处于可测状态。

四、输入控制与系统级操作,是做前置准备和恢复时最常用的一层

移动端测试里的不少动作,其实不需要依赖 UI 框架才能完成。尤其是设备准备、简单输入、系统级控制,ADB 往往更直接。

常见命令包括:

1
2
3
4
5
6
7
adb -s <serial> shell input tap 540 1680
adb -s <serial> shell input text hello123
adb -s <serial> shell input keyevent 3
adb -s <serial> shell input keyevent 4
adb -s <serial> shell input keyevent 82
adb -s <serial> shell wm size
adb -s <serial> shell dumpsys window | grep mCurrentFocus

这一层能力在测试中最典型的用途有四种。

1. 做简单前置准备

比如:

  • 解锁屏幕
  • 回到桌面
  • 点击开发测试入口
  • 输入账号或验证码

这些动作如果只是作为用例前置,没必要都交给完整 UI 自动化框架。

2. 快速恢复现场

用例跑挂以后,最常见的问题不是“功能错了”,而是设备现场脏了,比如:

  • 还停留在异常页
  • 弹窗没关
  • 键盘挡住了控件
  • 应用在后台挂起

这时候 keyevent、点击、强杀进程、回桌面这些动作非常实用。

3. 判断当前页面是不是对的

仅靠截图不一定能准确判断应用当前处于哪个页面。更常用的是:

1
2
adb -s <serial> shell dumpsys window
adb -s <serial> shell dumpsys activity activities

用它去看当前焦点页面、前台 Activity、任务栈状态,定位问题比纯看图快很多。

4. 做轻量化页面跳转

有些页面根本没必要从首页手点进去,而是可以直接通过 Activity、深链、广播去拉起。只要业务允许,这种方式会比纯 UI 点击更稳、更快。

但这里要注意一个边界:ADB 输入适合做辅助控制,不适合替代完整 UI 校验。
如果你的目标是验证页面元素、交互反馈、布局显示、控件状态,那还是应该交给更上层的自动化框架。

五、日志、截图、录屏和文件拉取,是失败后最值钱的证据链

的自动化脚本最大的问题不是执行失败,而是失败后没有证据。最终只能看到一行“case failed”,然后再让人手工复现。

这类问题最适合通过 ADB 提前补上证据链。

常用命令有:

1
2
3
4
5
6
adb -s <serial> logcat -d
adb -s <serial> logcat -c
adb -s <serial> exec-out screencap -p
adb -s <serial> shell screenrecord /sdcard/fail.mp4
adb -s <serial> pull /sdcard/fail.mp4 .
adb -s <serial> pull /sdcard/Android/data/com.example.app/files/logs .

1. 日志采集要区分“实时跟随”和“失败导出”

logcat 最常见的一种用法,就是把日志一股脑打印到控制台。这在个人排查时没问题,但放进自动化体系里不够用。

更稳的做法通常是分成两种模式:

  • 执行前清空日志,执行中持续采集
  • 失败后按设备和任务导出日志快照

这样做的好处是:

  • 日志上下文更干净
  • 更容易定位本次执行相关事件
  • 可以把日志和截图、录屏一起挂到测试报告

2. 截图不要只在最后截一张

如果只在 case 失败后截一张图,很可能现场已经变了。更合理的方式是:

  • 关键步骤失败立即截图
  • 异常恢复前截图
  • 恢复后再补一张图

这样你能知道失败发生在哪个阶段,也能知道恢复动作有没有生效。

3. 录屏适合定位间歇性问题,但要注意成本

录屏很有价值,尤其适合:

  • 间歇性闪退
  • 页面卡顿
  • 动画或过渡异常
  • 偶发点击失效

但它也有明显成本:

  • 文件大
  • 长时间录制影响设备性能
  • 不及时清理会占满设备空间

所以录屏通常更适合作为失败补救能力,而不是默认全量开启。

六、文件推送与结果回收,是 没意识到的高频能力

除了操作设备本身,ADB 还有一类很常用但经常被低估的能力,就是文件交互。

常见命令:

1
2
3
adb -s <serial> push local.json /sdcard/Download/local.json
adb -s <serial> pull /sdcard/Download/result.json .
adb -s <serial> shell ls /sdcard/Download

这类能力在测试里很常见,比如:

  • 下发调试配置
  • 导入 mock 数据
  • 推送测试资源文件
  • 拉取应用日志
  • 拉取执行产物

很多临时排查动作,其实都可以先通过文件下发或回收完成,不一定非得让研发另开接口。

但这里有两个常见坑:

1. 路径权限和 Android 版本差异

不同 Android 版本、不同厂商 ROM,在外部存储访问上差异很大。如果脚本里把路径写死,换一台设备就可能失效。

2. 文件拉取成功,不代表内容有效

我见过不少脚本只判断 pull 是否成功,但实际拉回来的日志文件是空的,或者是前一次执行残留的数据。
所以文件回收不仅要判断“命令成功”,还要判断:

  • 文件是否存在
  • 文件大小是否合理
  • 时间戳是否匹配本次执行

七、异常恢复任务要单独设计,不要把清理动作散落到各处

设备异常是移动端自动化里最常见的现实问题。如果没有统一恢复任务,脚本会越来越脏,因为每个人都会在不同位置偷偷塞一段“兜底命令”。

更合适的做法是专门定义 recover_device 这类任务,把恢复动作集中起来。

常见恢复动作包括:

1
2
3
4
5
adb -s <serial> shell input keyevent 3
adb -s <serial> shell am force-stop com.example.app
adb -s <serial> shell pm clear com.example.app
adb -s <serial> shell rm -f /sdcard/fail.mp4
adb -s <serial> reboot

恢复任务通常分三层:

1. 轻恢复

  • 回桌面
  • 关闭目标应用
  • 清临时文件
  • 重新拉起应用

适合绝大多数普通失败场景。

2. 中恢复

  • 清应用数据
  • 重启 ADB 会话
  • 重新识别设备

适合应用状态明显脏掉,但设备本身还正常的场景。

3. 重恢复

  • 重启设备
  • 标记设备不可调度
  • 通知维护人介入

适合频繁 offline、录屏失效、系统卡死、存储异常等问题。

这套分层很重要,因为不是每次失败都值得直接重启设备。恢复成本太高,执行效率会被拖垮;恢复太轻,又会导致污染环境带进下一轮执行。

八、在项目里最常见的几类 ADB 实战坑

如果只在本机连一台设备跑几次脚本,ADB 看起来很稳定。但一旦进入批量执行、长时间运行、多人协作环境,下面这些坑会高频出现。

坑 1:命令能跑通,但结果不可靠

现象:

  • install 成功了,但应用打不开
  • pull 成功了,但拉回的是空文件
  • screenrecord 没报错,但视频无法播放

根因往往不是“命令语法错了”,而是:

  • 执行完成后没有校验结果
  • 只看退出码,不看输出内容
  • 文件是旧产物,不是本次执行生成的

修复思路:

  • 每个关键任务都要定义结果校验
  • 尽量检查输出内容、文件大小、时间戳、进程状态
  • 不要把“命令执行成功”等同于“业务动作成功”

坑 2:多设备执行时,忘了强制带 -s

现象:

  • 命令偶尔成功,偶尔跑到别的设备上
  • 日志和截图对不上用例
  • 设备池里互相串数据

这是非常典型的工程事故。只要环境里可能同时连多台设备,所有设备命令都必须强制带序列号
这一点不能靠开发者自觉,必须在封装层统一做掉。

坑 3:日志越采越多,最后把机器和设备都拖慢

现象:

  • 流水线越来越慢
  • 日志文件越来越大
  • 某些设备开始安装失败或截图失败

最后查下来,往往是:

  • logcat 没清理
  • 录屏文件没删除
  • 拉取日志后本地没归档清理
  • 设备空间长期积压

这类问题本质上不是工具问题,而是资产清理策略没设计。

坑 4:把 ADB 当万能方案,结果边界失控

ADB 很强,但它不是所有移动端测试问题的解法。

比如:

  • 元素定位准确性
  • 页面渲染断言
  • 复杂交互链路
  • iOS 端统一方案

这些事情只靠 ADB 不够。
如果团队把所有移动端自动化都压到 ADB 上,最后会发现可维护性越来越差。它更适合作为设备层和辅助控制层,而不是替代一切。

九、更推荐的 ADB 能力分层方式

如果后面要接测试框架、Jenkins 或自研平台,建议把 ADB 这层能力至少分成下面三层。

1. 原子命令层

职责:

  • adb
  • 处理超时
  • 捕获标准输出和标准错误
  • 统一错误码

这一层不关心业务,只关心“命令有没有稳定执行”。

2. 任务能力层

职责:

  • 设备检查
  • 应用安装
  • 清数据
  • 拉起应用
  • 截图
  • 录屏
  • 日志导出
  • 设备恢复

这一层面向测试任务,是最值得沉淀复用的部分。

3. 场景编排层

职责:

  • 冒烟前置准备
  • case 失败自动采证
  • 批量设备巡检
  • 执行后环境回收

这一层和具体业务、测试流程绑定更紧。

分层的意义在于:

  • 原子命令层坏了,容易替换
  • 任务能力层稳定后,可被多个项目复用
  • 场景编排层可以随着业务变化灵活调整

十、排查 ADB 问题时,可以按什么顺序走

这部分很重要。的问题不是不会写命令,而是一出故障就开始盲试。

通常可以按这条顺序排查:

1. 先看设备状态

确认:

  • 是否能被识别
  • 是否 device
  • 是否被别的任务占用

2. 再看命令有没有发到正确设备

确认:

  • 是否带了 -s
  • 序列号是否正确
  • 多设备环境是否串行/并行混乱

3. 再看执行动作是否真的完成

确认:

  • 安装后应用是否存在
  • 启动后进程是否在
  • 文件是否真的生成
  • 截图和日志是否是本次产物

4. 最后再看业务问题

很多所谓“业务失败”,其实在第三步之前就已经暴露出是设备层或证据层问题。
如果一开始就往业务逻辑上猜,排查会绕很远。

结语

ADB 在测试里最有价值的地方,不是“会不会敲几个命令”,而是你能不能把这些命令变成稳定、统一、可排障、可接入流水线的测试能力。

如果只是个人调试,命令会用就够了;但如果你希望它服务于团队、服务于自动化体系,就必须把它从“命令集合”升级成“任务能力层”。

这也是更准确地说的移动端工程化起点:

  • 不追求一上来就做复杂平台
  • 先把设备层能力做稳定
  • 先把证据链补齐
  • 先把恢复动作标准化

只有这样,后面无论你接 Appium、接设备池,还是接更复杂的移动端自动化框架,底层才不会一直塌。