mcpField
R
running-instrumented-tests-via-adb

running-instrumented-tests-via-adb

github.com/skydoves测试与质量33975 次安装22 周前更新
代码仓库

关于 running-instrumented-tests-via-adb

该技能面向负责 Android 自动化测试的开发者,讲解如何直接运行 adb shell am instrument,而不依赖 Gradle 执行测试。它涵盖测试筛选、分片、AndroidX Test Orchestrator 配置,以及通过解析输出判断失败。

主要功能

  1. 01直接运行 am instrument
  2. 02按类、方法或注解筛选测试
  3. 03使用 numShards 和 shardIndex 分片
  4. 04配置 AndroidX Test Orchestrator
  5. 05解析测试状态输出检测失败

适用场景

  1. 01在脚本中运行单个测试方法
  2. 02将测试套件分发到多台设备
  3. 03构建并行执行的 CI 测试任务
  4. 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.orchestrator APK(通常通过 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 — AGP androidTest 的默认运行器。
    • 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 是目标。

  • 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。
  • 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(AOSP Instrument.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)。

参考资料

评论

支持 Markdown