mcpField
R
running-instrumented-tests-via-adb

running-instrumented-tests-via-adb

github.com/skydovesテスト・品質33975 回インストール22 週間前に更新
リポジトリ

running-instrumented-tests-via-adb について

Gradle に頼らず `adb shell am instrument` で実行する方法を扱う、Android テスト自動化向けのスキルです。テスト選択、分割実行、AndroidX Test Orchestrator の設定、出力解析による失敗検出を説明します。

主な機能

  1. 01am instrument の直接実行
  2. 02クラス、メソッド、アノテーションによる選択
  3. 03numShards と shardIndex による分割
  4. 04AndroidX Test Orchestrator の設定
  5. 05ステータス出力の解析による失敗検出

活用シーン

  1. 01スクリプトから単一テストメソッドを実行
  2. 02複数デバイスへのテストスイートの分散
  3. 03並列 CI テストジョブの構築
  4. 04Orchestrator 引数設定の問題の調査

インストール

コマンドをコピーしてターミナルで実行します。

$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 を省略することです。その場合、終了コードに意味がなくなります。2 番目に多い落とし穴は、オーケストレーターとターゲットの関係を逆にすることです。

このスキルを使う場合

  • スクリプトから単一のテストメソッドを実行したい場合:adb shell am instrument -w -r -e class com.example.MyTest#myMethod ...。
  • numShards/shardIndex を使って、テストスイートを N 台のデバイスに分割したい場合。
  • clearPackageData true を指定して AndroidX Test Orchestrator を設定しているが、ターゲットと targetInstrumentation の順序がわからなくなった場合。
  • テストが実際には失敗しているのに、INSTRUMENTATION_STATUS_CODE: -2 で失敗が示されていても $? が 0 となるため、CI スクリプトが成功と報告する場合。
  • テストの実行前に、デバイス上のランナーにデバッガーのアタッチを待たせたい場合。

このスキルを使わない場合

  • テスト対象のアプリをインストールまたはリセットしたい場合は、../../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 の密閉型テスト環境の設定を参照してください)。

ワークフロー

  • 1. 標準的なコマンドを組み立てます。 developer.android.com/studio/test/command-line と am help からの引用です。

    adb shell am instrument -w -r \
      [-e <key> <value>] ... \
      <pkg>/<runner>
    
    • -w は「戻る前にインストルメンテーションの完了を待つ。テストランナーには必須です。」という意味です。指定しないとシェルはすぐに戻り、$? は意味を持ちません。
    • -r は「生の結果を出力する(指定しない場合は report_key_streamresult をデコードする)」という意味です。CI で扱いやすい出力にするため、-w と組み合わせてください。

    よく使われるランナー:

    • 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 です。
    disableAnalyticstrueAndroidJUnitRunner の使用状況統計を無効にします。
    clearPackageDatatrueAndroidX Orchestrator 専用:テスト間で pm clear を実行します。
    targetInstrumentation<pkg>/<runner>AndroidX Orchestrator 専用:ラップするインストルメンテーションです。
    coveragetrueJaCoCo のカバレッジを収集します。
    coverageFile<path>デバイス上のカバレッジファイルの場所を上書きします。

    使用例:

    # 1 つのクラス。
    adb shell am instrument -w -r \
      -e class com.example.app.LoginTests \
      com.example.app.test/androidx.test.runner.AndroidJUnitRunner
    
    # 1 つのメソッド。
    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 は各テストを個別のインストルメンテーション呼び出しで実行し、クラッシュを分離します。また、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
    

    よくあるコピー&ペーストの間違いは、2 つを逆にすることです。AndroidJUnitRunner を位置引数に指定し、Orchestrator を targetInstrumentation に指定してしまいます。ランナーが Orchestrator で、ターゲットが AndroidJUnitRunner であるため、分かりにくいエラーが発生します。

  • 4. 出力のフレーミングを確認します。 正確な形式は次のとおりです。

    INSTRUMENTATION_STATUS: <key>=<value>      # 各ステータスフレームのBundleエントリーごとに繰り返し
    INSTRUMENTATION_STATUS_CODE: <int>          # 各ステータスフレームにつき1つ
    ...
    INSTRUMENTATION_RESULT: <key>=<value>      # 最終結果のBundleエントリーごとに繰り返し
    INSTRUMENTATION_CODE: <int>                 # 最後に1回のみ
    

    ステータスコードの値(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 は、フレームワークの標準4値に -3 と -4 を追加します。フレームワークのコードのみでは、テストごとの INSTRUMENTATION_STATUS_CODE に 1、0、-1、-2 だけが出力されます。最後の INSTRUMENTATION_CODE 行は別の値で、Android の Activity.RESULT_* 定数を使用します。実行がランナーレベルのエラーなしに完了した場合は -1(RESULT_OK)、ランナー自体でエラーが発生した場合は 0(RESULT_CANCELED)です。この行もシェルの $? も、テストの成功/失敗を確実に示すものではありません。AndroidJUnitRunner は常に finish(Activity.RESULT_OK, results) を呼び出し、AOSP の frameworks/base/cmds/am/.../Instrument.java は run() を無条件の System.exit(0) で終了します。そのため、テストが失敗しても $? は 0 になります。CI スクリプトでは、失敗を検出するために、標準出力から INSTRUMENTATION_STATUS_CODE: -2(失敗)と INSTRUMENTATION_STATUS_CODE: -1(エラー)の行を解析する必要があります。標準的な grep の書き方については手順 6 を参照してください。

    ステータスフレームごとの標準 Bundle キー(Instrumentation リファレンスより):

    • REPORT_KEY_NAME_CLASS — 現在のテストの完全修飾クラス名。
    • REPORT_KEY_NAME_TEST — 現在のテストメソッド。
    • REPORT_KEY_NUM_CURRENT — 現在のテストの1始まりのインデックス。
    • REPORT_KEY_NUM_TOTAL — テストの総数。
    • REPORT_KEY_STACK — 失敗時のスタックトレース。
    • REPORT_KEY_STREAMRESULT — 整形済みストリーム出力(デフォルトではデコード済み。未加工のままにするには -r を指定します)。
  • 5. シェルの終了コードを成功/失敗の判定に使わないでください。 -w を指定すると、全体が成功した場合もテストが失敗した場合も、$? は 0 を返します。am instrument は System.exit(0) で終了し、AndroidJUnitRunner の finish() はテスト結果にかかわらず RESULT_OK を報告するためです。-w を指定しない場合、シェルはすぐに戻るため、$? はさらに意味を持ちません。手順 6 を参照し、標準出力から INSTRUMENTATION_STATUS_CODE: -2(失敗)と -1(エラー)の行を解析する必要があります。-w -r は引き続き必要です(AGP/Gradle と Android Studio が指定して実行する待機フラグと未加工フラグです)。ただし、終了コードの判定には使いません。

  • 6. CI 用に出力を解析します。 実用的な方法は2つあります。

    • すばやく処理できる 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 を実行します。2つの方法は併用できます。多くのパイプラインでは、シャーディング実行のために 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. シェルの終了コード伝播には制限があることに注意します。 adb shell の終了コード伝播が信頼できるのは、API 24/Platform Tools 24 以降に限られます。それ以前の組み合わせでは、adb shell <cmd> の $? に依存するスクリプトは、デバイス上で値を取得して(echo $? > /sdcard/exit)、それを pull する必要があります。最新のデバイスと 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).
パターン:誤りと正しい例 — 2 台のデバイス間でのシャーディング
# 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 を渡します(標準出力の解析に必要です)。am instrument はテスト結果にかかわらず System.exit(0) を呼び出すため、$? で判定しては いけません。
  • CI で使用する場合は、-w と -r を 必ず組み合わせます。生の出力は解析できますが、デコード済みの report_key_streamresult 形式は解析できません。
  • 単一メソッドを選択する場合は、<class>#<method>(ハッシュ区切り)を 必ず使用します。コロンやドットでは、エラーなく失敗します。
  • オーケストレーターは ランナー(位置引数の最後)に指定し、AndroidJUnitRunner は -e targetInstrumentation で ターゲットとして指定します。逆に指定しては いけません。
  • -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> の行がちょうど 1 つあること。
  • テストごとのフレームに、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> でテストメソッドがちょうど 1 つ実行されること(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 Managed Devices、シャーディング):https://developer.android.com/studio/test/advanced-test-setup
  • tasks/research/A2-adb-shell-commands.md — -e の完全な一覧、AndroidJUnitRunner の -3/-4 を含むステータスコードの値、オーケストレーターの接続方法、終了コードが返されるのは -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

コメント