关于 running-instrumented-tests-via-adb
该技能面向负责 Android 自动化测试的开发者,讲解如何直接运行 adb shell am instrument,而不依赖 Gradle 执行测试。它涵盖测试筛选、分片、AndroidX Test Orchestrator 配置,以及通过解析输出判断失败。
主要功能
- 01直接运行 am instrument
- 02按类、方法或注解筛选测试
- 03使用 numShards 和 shardIndex 分片
- 04配置 AndroidX Test Orchestrator
- 05解析测试状态输出检测失败
适用场景
- 01在脚本中运行单个测试方法
- 02将测试套件分发到多台设备
- 03构建并行执行的 CI 测试任务
- 04排查 Orchestrator 参数配置错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ skydoves/ android-testing-skills --skill running-instrumented-tests-via-adb安装后在 Claude Code 中输入/running-instrumented-tests-via-adb确认已加载。
详细介绍
通过 ADB 运行插桩测试 — 无需 Gradle 的 am instrument
adb shell am instrument 是 Gradle 调用的底层命令;对于自行管理 APK 的 CI 脚本、需要将测试分片并分发到多台设备上运行的场景,以及针对单个测试方法进行快速循环调试,直接运行该命令才是合适的做法。许多 CI 脚本容易犯的错误是漏掉 -w,导致退出代码失去意义。第二常见的错误是弄反 Orchestrator 与目标之间的关系。
何时使用此技能
- 用户想通过脚本运行单个测试方法:
adb shell am instrument -w -r -e class com.example.MyTest#myMethod ...。 - 用户想使用
numShards/shardIndex将测试套件分片并分发到 N 台设备上。 - 用户正在配置 AndroidX Test Orchestrator 和
clearPackageData true,但搞混了目标与targetInstrumentation的顺序。 - 用户的 CI 脚本在测试实际失败时仍显示成功,因为尽管
INSTRUMENTATION_STATUS_CODE: -2表明发生了失败,$?仍为0。 - 用户希望设备上的运行器在运行测试前等待调试器连接。
何时不使用此技能
- 用户想安装或重置被测应用 — 请使用
../../apps/installing-and-managing-apps/SKILL.md。 - 用户想选择设备、等待设备启动,或设置 Wi-Fi 调试 — 请使用
../../devices/connecting-to-devices/SKILL.md和../../devices/connecting-over-wifi/SKILL.md。 - 用户正在编写 JUnit4 /
AndroidJUnit4测试类本身 — 请使用../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md。 - 用户希望由 Gradle 执行测试(
./gradlew connectedDebugAndroidTest)— 该方式也会读取testInstrumentationRunnerArguments中的插桩运行器参数。
先决条件
- 已安装应用 APK(
com.example.app)和测试 APK(com.example.app.test)— 请参阅../../apps/installing-and-managing-apps/SKILL.md。 - 构建测试 APK 时允许使用
-t(android:testOnly="true")。 - 对于 Test Orchestrator:已安装
androidx.test.orchestratorAPK(通常通过androidTestUtil("androidx.test:orchestrator:1.6.1")和adb install -r androidx.test.orchestrator.apk安装)。 - 设备处于
device状态,且已停用动画,以确保运行稳定(请参阅docs/CORPUS.md§I.6 hermetic 测试设置)。
工作流
-
1. 组合标准命令。 原文摘自 developer.android.com/studio/test/command-line 和
am help:adb shell am instrument -w -r \ [-e <key> <value>] ... \ <pkg>/<runner>-w“返回前等待插桩运行结束。测试运行器必须使用此选项。”如果不使用此选项,shell 会立即返回,$?也就毫无意义。-r“打印原始结果(否则解码report_key_streamresult)。”与-w搭配使用,可获得适合 CI 的输出。
常见运行器:
androidx.test.runner.AndroidJUnitRunner— AGPandroidTest的默认运行器。androidx.test.orchestrator.AndroidTestOrchestrator— 包装器,会在各自独立的插桩调用中运行每项测试。
直接调用 AndroidJUnitRunner:
adb shell am instrument -w -r \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner -
2. 使用
-e参数表进行筛选、分片和运行器行为配置。 AndroidJUnitRunner / AndroidX Test 支持的完整参数表:键 值 含义 package<java_package>“完全限定的 Java 包名。此包中的任何测试类都会执行。”优先级高于 class。多个值用逗号分隔。class<fqcn>“仅执行此测试用例类。” class<fqcn>#<method>“仅执行此方法。”使用井号作为分隔符。 class<fqcn1>,<fqcn2>#m,...用逗号分隔,以组合多个筛选条件。 sizesmall/medium/large“运行使用 @SmallTest、@MediumTest或@LargeTest注解的测试方法。”annotation<fqcn>筛选带有此注解的测试。 notAnnotation<fqcn>排除带有此注解的测试。 numShards<N>并行运行时的分片总数。 shardIndex<i>此分片从 0 开始的索引(0 ≤ i < N)。 debugtrue“以调试模式运行测试。”每个测试开始前等待调试器连接。 logtrue“加载并记录所有指定的测试,但不运行它们。”可用于验证筛选条件组合。 listener<fqcn>注册 RunListener。多个值用逗号分隔。filter<fqcn>注册自定义 JUnit Filter。runnerBuilder<fqcn>自定义 RunnerBuilder。disableAnalyticstrue禁用 AndroidJUnitRunner 使用情况统计。 clearPackageDatatrue仅限 AndroidX Orchestrator:在测试之间执行 pm clear。targetInstrumentation<pkg>/<runner>仅限 AndroidX Orchestrator:被包装的 instrumentation。 coveragetrue收集 JaCoCo 覆盖率。 coverageFile<path>覆盖设备上的覆盖率文件位置。 示例:
# 一个类。 adb shell am instrument -w -r \ -e class com.example.app.LoginTests \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner # 一个方法。 adb shell am instrument -w -r \ -e class com.example.app.LoginTests#login_succeeds \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner # 仅运行 @LargeTest。 adb shell am instrument -w -r -e size large \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner # 4 个分片中的第 0 个(在 4 台设备上并行运行,速度提升 4 倍)。 adb shell am instrument -w -r -e numShards 4 -e shardIndex 0 \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner # 每个测试开始前等待调试器连接。 adb shell am instrument -w -e debug true \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner # 自定义 RunListener。 adb shell am instrument -w -r \ -e listener com.example.app.MyRunListener \ com.example.app.test/androidx.test.runner.AndroidJUnitRunner -
3. 正确配置 AndroidX Test Orchestrator。 Orchestrator 会为每个测试单独启动一次 instrumentation,以隔离崩溃,并且(使用
clearPackageData时)在测试之间清除应用状态。使用方法:- 目标(最后一个位置参数)是 orchestrator:
androidx.test.orchestrator/.AndroidTestOrchestrator。 - 通过
-e targetInstrumentation <pkg>/<runner>传入被包装的运行器。
adb shell am instrument -w -r \ -e clearPackageData true \ -e targetInstrumentation com.example.app.test/androidx.test.runner.AndroidJUnitRunner \ androidx.test.orchestrator/.AndroidTestOrchestrator常见的复制粘贴错误是把两者颠倒——将 AndroidJUnitRunner 放在位置参数中,并将 orchestrator 放进
targetInstrumentation。这会导致难以理解的错误,因为 orchestrator 才是运行器,而 AndroidJUnitRunner 是目标。 - 目标(最后一个位置参数)是 orchestrator:
-
4. 阅读输出格式。 原样格式:
INSTRUMENTATION_STATUS: <key>=<value> # 每个状态帧中每个 Bundle 条目重复一次 INSTRUMENTATION_STATUS_CODE: <int> # 每个状态帧一行 ... INSTRUMENTATION_RESULT: <key>=<value> # 最终结果中每个 Bundle 条目重复一次 INSTRUMENTATION_CODE: <int> # 最终行,且只出现一次状态码值(依据
Instrumentation.REPORT_VALUE_RESULT_*,以及 AndroidJUnitRunner 的扩展):代码 含义 1测试已启动( REPORT_VALUE_RESULT_START)。0测试通过( REPORT_VALUE_RESULT_OK)。-1进程错误 / 意外抛出异常( REPORT_VALUE_RESULT_ERROR)。-2断言失败( REPORT_VALUE_RESULT_FAILURE)。-3已忽略( @Ignore)——由 AndroidJUnitRunner 添加。-4假设失败( org.junit.AssumeViolatedException)——由 AndroidJUnitRunner 添加。AndroidJUnitRunner 在框架的四个规范值之上添加了
-3和-4;纯框架代码针对每个测试的INSTRUMENTATION_STATUS_CODE只会输出1、0、-1、-2。最终的INSTRUMENTATION_CODE行是独立的,使用 Android 的Activity.RESULT_*常量——运行完成且没有运行器级错误时为-1(RESULT_OK),运行器本身出错时为0(RESULT_CANCELED)。此行和 shell 的$?都不能可靠地报告测试通过或失败:AndroidJUnitRunner 无论如何都会调用finish(Activity.RESULT_OK, results),而 AOSP 的frameworks/base/cmds/am/.../Instrument.java会以无条件的System.exit(0)结束run()——因此,即使测试失败,$?仍为0。CI 脚本必须解析标准输出中的INSTRUMENTATION_STATUS_CODE: -2(失败)和INSTRUMENTATION_STATUS_CODE: -1(错误)行,以检测失败。规范的 grep 用法见第 6 步。每个状态帧中的标准 Bundle 键(来自
Instrumentation参考文档):REPORT_KEY_NAME_CLASS— 当前测试的 FQCN。REPORT_KEY_NAME_TEST— 当前测试方法。REPORT_KEY_NUM_CURRENT— 当前测试的从 1 开始的索引。REPORT_KEY_NUM_TOTAL— 测试总数。REPORT_KEY_STACK— 失败堆栈跟踪。REPORT_KEY_STREAMRESULT— 格式化后的流输出(默认已解码;传入-r可保留原始内容)。
-
5. 不要将 shell 退出码视为测试通过或失败的信号。 使用
-w时,无论整体通过还是测试失败,$?都会报告0——am instrument会以System.exit(0)结束,且 AndroidJUnitRunner 的finish()无论测试结果如何都会报告RESULT_OK。不使用-w时,shell 会立即返回,此时$?的意义更小。必须解析标准输出中的INSTRUMENTATION_STATUS_CODE: -2(失败)和-1(错误)行——见第 6 步。仍然需要-w -r(AGP/Gradle 和 Android Studio 调用时会使用等待和原始输出标志),但不能依赖它的退出码。 -
6. 解析输出以供 CI 使用。 有两种实用方案:
- 使用可 grep 的行,适用于快速流水线:
adb shell am instrument -w -r ... \ | tee instrument.log grep -E '^INSTRUMENTATION_(CODE|STATUS_CODE|RESULT)' instrument.log grep '^INSTRUMENTATION_STATUS_CODE: -2' instrument.log && exit 1 - 让 Gradle 生成 XML,路径为
app/build/outputs/androidTest-results/connected/<variant>/TEST-*.xml——与 JUnit XML 测试报告工具(大多数 CI 控制面板)集成时必需。使用./gradlew connectedDebugAndroidTest生成此文件。两种方案并不互斥;许多流水线会直接运行am instrument来执行分片,然后自行将原始状态流后处理为 JUnit XML。
- 使用可 grep 的行,适用于快速流水线:
-
7. 为隔离运行禁用动画并重置状态。 根据
docs/CORPUS.md§I.6:adb shell settings put global window_animation_scale 0 adb shell settings put global transition_animation_scale 0 adb shell settings put global animator_duration_scale 0 adb shell pm clear com.example.app # 每次运行前执行;编排器会在每项测试前执行此操作或通过 Gradle:
testOptions.animationsDisabled = true,并使用 Test Orchestrator 的-e clearPackageData true。 -
8. 注意 shell 退出码传播的限制。 只有从 API 24 / Platform Tools 24 开始,
adb shell的退出码传播才可靠。在较旧的组合中,依赖$?获取adb shell <cmd>返回值的脚本需要在设备端捕获该值(执行echo $? > /sdcard/exit),再将其拉取回来。对于现代设备和 Platform Tools 24 及更高版本,adb shell <cmd>; echo $?会按预期传播设备端的退出码——但请注意,对于am instrument,无论测试结果如何,该代码始终为0(步骤 5),因此传播退出码与判断通过或失败无关。
模式
模式:错误做法与正确做法 — 不带 -w 的退出码
# WRONG
adb shell am instrument -r \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner
echo $?
# 0
# WRONG because: -w is omitted, so the adb shell returns immediately. $?
# reflects the launch, not the test outcome. Failures in the test run are
# silently lost.
# RIGHT — gate on stdout, NOT on $?
output=$(adb shell am instrument -w -r \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner)
if echo "$output" | grep -qE 'INSTRUMENTATION_STATUS_CODE: -[12]$'; then
echo "FAILED"; exit 1
fi
# am instrument exits 0 even when tests fail (System.exit(0) in AOSP Instrument.java),
# so the only reliable signal is parsing INSTRUMENTATION_STATUS_CODE: -2 (failure) or -1 (error).
模式:错误做法与正确做法 — 切换编排器目标 / targetInstrumentation
# WRONG
adb shell am instrument -w -r \
-e clearPackageData true \
-e targetInstrumentation androidx.test.orchestrator/.AndroidTestOrchestrator \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner
# WRONG because: the orchestrator is the runner, not the target. AndroidJUnitRunner
# does not know how to invoke an "orchestrator target", and clearPackageData is
# not a recognised arg of AndroidJUnitRunner — only of the orchestrator.
# RIGHT
adb shell am instrument -w -r \
-e clearPackageData true \
-e targetInstrumentation com.example.app.test/androidx.test.runner.AndroidJUnitRunner \
androidx.test.orchestrator/.AndroidTestOrchestrator
# Orchestrator is the runner (last positional arg).
# AndroidJUnitRunner is the wrapped target (passed via -e targetInstrumentation).
模式:错误做法与正确做法 — 在两台设备间分片
# WRONG
adb -s emulator-5554 shell am instrument -w -r -e numShards 2 -e shardIndex 0 \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner &
adb -s emulator-5554 shell am instrument -w -r -e numShards 2 -e shardIndex 1 \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner &
wait
# WRONG because: both shards run on the same device (-s emulator-5554). They
# serialize against each other through am, defeating the parallelism goal of
# sharding. Worse, they may collide on the same app under test.
# RIGHT
adb -s emulator-5554 shell am instrument -w -r -e numShards 2 -e shardIndex 0 \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner &
adb -s emulator-5556 shell am instrument -w -r -e numShards 2 -e shardIndex 1 \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner &
wait
# One shard per device. Each device runs roughly half the suite.
模式:错误做法与正确做法 — 运行单个方法
# WRONG
adb shell am instrument -w -r -e class com.example.app.LoginTests:login_succeeds \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner
# WRONG because: the separator between class and method is `#`, not `:`.
# AndroidJUnitRunner treats the whole string as a class name and finds nothing.
# RIGHT
adb shell am instrument -w -r -e class com.example.app.LoginTests#login_succeeds \
com.example.app.test/androidx.test.runner.AndroidJUnitRunner
强制规则
- 必须在任何 CI / 脚本调用中为
am instrument传入-w,以便 shell 等待运行器完成(这是解析标准输出的必要条件)。不得依据$?作出判断——无论测试结果如何,am instrument都会调用System.exit(0)。 - 必须在 CI 使用时将
-w与-r配对——原始输出可解析;解码后的report_key_streamresult格式不可解析。 - 必须使用
<class>#<method>(井号分隔符)来选择单个方法。冒号或点号会导致操作静默失败。 - 必须将编排器设为运行器(位置参数中的最后一个参数),并通过
-e targetInstrumentation将 AndroidJUnitRunner 设为目标——不能反过来。 - 不得在省略
-w时依赖am instrument退出码。 - 不得假设不使用 Orchestrator 时
clearPackageData true也能生效——这是仅供 Orchestrator 使用的参数。 - **推荐:**在 CI 中使用 Test Orchestrator +
clearPackageData true实现隔离的测试环境;这对应于 Gradle 的testOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR"。 - **推荐:**在运行前禁用动画(
settings put global *_animation_scale 0),或使用 Gradle 的testOptions.animationsDisabled = true。
验证
- 没有脚本依据
am instrument的$?作出判断——它始终为0(AOSPInstrument.java中的System.exit(0);AndroidJUnitRunner 中的finish(RESULT_OK, …))。通过在标准输出中搜索INSTRUMENTATION_STATUS_CODE: -1(错误)/-2(失败)来检测通过或失败。 - 输出流末尾恰好包含一行
INSTRUMENTATION_CODE: <int>。 - 每项测试的帧都包含匹配的
INSTRUMENTATION_STATUS_CODE: 1(开始),随后为0(正常)或-1/-2/-3/-4。 - 使用 Orchestrator 时,运行器位置参数为
androidx.test.orchestrator/.AndroidTestOrchestrator,且-e targetInstrumentation指定 AndroidJUnitRunner 的 FQN。 - 分片运行时,
-e numShards N -e shardIndex i在不同设备上运行(每个分片的-s参数不同)。 -
-e class <fqcn>#<method>只运行一个测试方法(通过INSTRUMENTATION_STATUS: numtests=1交叉核对)。 - 设备上的动画缩放值为
0.0(adb shell settings get global window_animation_scale)。
参考资料
- 从命令行运行测试(
am instrument):https://developer.android.com/studio/test/command-line - ADB 用户指南(
am instrument标志表):https://developer.android.com/tools/adb#am - AndroidX Test Orchestrator:https://developer.android.com/training/testing/instrumented-tests/androidx-test-libraries/runner
Instrumentation参考(报告键、状态码):https://developer.android.com/reference/android/app/Instrumentation- 高级测试设置(Gradle 托管设备、分片):https://developer.android.com/studio/test/advanced-test-setup
tasks/research/A2-adb-shell-commands.md— 完整的-e表、状态码值(包括 AndroidJUnitRunner 的-3/-4)、Orchestrator 配置,以及仅在使用-w时才会返回退出码的规则。docs/CORPUS.md§I.5(am instrument调用)、§I.6(隔离式测试设置)、§I.10(-w退出码规则)。- 相关技能:
- 高层架构:
../../architecture/understanding-adb-architecture/SKILL.md - 连接设备/等待启动:
../../devices/connecting-to-devices/SKILL.md - 无线 ADB:
../../devices/connecting-over-wifi/SKILL.md - 安装/清除/列出应用:
../../apps/installing-and-managing-apps/SKILL.md
- 高层架构:
- 跨集合相关内容:
- 使用
AndroidJUnit4运行插桩测试:../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md - 在 Android 上配置 JUnit4:
../../../jvm-tests/runner/configuring-junit4-on-android/SKILL.md - 源代码集策略:
../../../fundamentals/strategies/organizing-test-source-sets/SKILL.md
- 使用