mcpField
E
extracting-test-artifacts

extracting-test-artifacts

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

extracting-test-artifacts について

ホストとデバイス間でテストファイルを移動する Android 開発者向けのスキルです。adb の権限規則、run-as によるバイナリの安全な取り出し、AndroidX TestStorage によるテストごとの成果物収集を扱います。

主な機能

  1. 01adb push と pull の使い方
  2. 02デバイスパスのアクセス権限
  3. 03run-as によるアプリ内ファイルへのアクセス
  4. 04exec-out によるバイナリの安全な抽出
  5. 05AndroidX TestStorage の成果物収集

活用シーン

  1. 01デバッグ可能なテストアプリからデータベースを取得
  2. 02スクリーンショットや JSON を CI 成果物として保存
  3. 03大きなテストデータをデバイスへ転送

インストール

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

$npx skills add https://github.com/skydoves/android-testing-skills --skill extracting-test-artifacts

インストール後、Claude Code で/extracting-test-artifactsを入力して読み込まれたか確認します。

詳細

テスト成果物の抽出 — adb pull / push と run-as

このスキルでは、ホストとデバイス間でファイルを移動する方法、pull の成功可否を左右するパスの権限ルール、テストごとの成果物の出力先を指定する androidx.test:services の TestStorage API について説明します。失敗時に何をいつ pull するかを調整する処理については、../../automation/scripting-adb-for-ci/SKILL.md を参照してください。

このスキルを使う場面

  • テストで生成された成果物(スクリーンショット、JSON、取得した DB など)を、ビルド出力として CI に取り込む必要がある場合。
  • adb pull /data/data/com.example/databases/main.db が「Permission denied」で失敗し、開発者が run-as の存在を知らない場合。
  • ホストに取り込んだバイナリファイル(PNG、MP4、SQLite DB)のバイト列が壊れている場合 — exec-out の指定漏れが原因です。
  • 大きな APK やテストデータファイルの push が遅く、最新の圧縮フラグ(-z brotli)を使いたい場合。
  • テストアプリがスクリーンショットを /sdcard/Android/data/com.example/files/screenshots/ に書き込み、API 30 以降で pull できるか知りたい場合(そのパッケージが所有していれば可能です)。
  • 「/sdcard に書き込み、ホスト側で pull」する方法から、最新の AndroidX TestStorage API への移行を検討している場合。

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

  • 成果物となるスクリーンショットや動画の撮影。../../capture/capturing-screenshots-and-screenrecord/SKILL.md を使ってください。
  • 抽出するログの取得。../../observability/extracting-logs-with-logcat/SKILL.md を使ってください。
  • APK のインストールまたはアンインストール。../../apps/installing-and-managing-apps/SKILL.md を使ってください。
  • デバイスの操作(タップ、キーイベント)。../../control/injecting-input-and-state/SKILL.md を使ってください。
  • CI 全体の調整(pull のタイミング、再試行、並列化)。../../automation/scripting-adb-for-ci/SKILL.md を使ってください。

前提条件

  • adb get-state が device を返すこと。../../devices/connecting-to-devices/SKILL.md を参照してください。
  • run-as を使う場合:対象 APK のマージ済みマニフェストで android:debuggable="true" が設定されていること(デフォルトの debug ビルドバリアント)。
  • -z brotli / zstd を使う場合:ホストに platform-tools 30 以降があり、デバイスの adbd が Android 11 以降であること。
  • TestStorage を使う場合:androidx.test:services と、am instrument に渡す -e useTestStorageService true 引数が必要です。

対称的な構文

「指定した場所に APK ファイルだけをコピーする install コマンドとは異なり、pull コマンドと push コマンドでは、任意のディレクトリやファイルをデバイス上の任意の場所にコピーできます。」 — developer.android.com/tools/adb

adb pull <remote> [<local>]      # device → host (defaults to cwd)
adb push <local>  <remote>       # host   → device

最新のフラグ(platform-tools ≥ 30)

push [--sync] [-z ALGORITHM] [-Z] LOCAL... REMOTE
pull [-a]     [-z ALGORITHM] [-Z] REMOTE... LOCAL
フラグ意味
-z <algo>圧縮を有効にします。<algo> ∈ any / none / brotli / lz4 / zstd。any を指定すると、adbd が選択します。
-Z圧縮を無効にします。
--sync(push のみ)タイムスタンプが異なるファイルのみ push します — 高速な増分更新が可能です。
-a(pull のみ)ファイルのタイムスタンプとモードを保持します。
-nドライラン。
-q進行状況メッセージを抑制します。

従来の adb pull -p(進行状況を表示)は、現在は何もしません — 最新の adb はデフォルトで進行状況を表示し、-q で非表示にできます。CI スクリプトで -p に頼らないでください。

パスの権限ルール

これは重要な表です。「Permission denied」という adb pull の失敗のほとんどは、このルールを誤解していることが原因です。

パスadb shell で書き込み可能かpull は動作するか
/data/local/tmp/はいはい
/sdcard/(= /storage/emulated/0/)はい(API 30 以降はスコープ付きストレージのルールに従います)はい
/data/data/<pkg>/run-as <pkg> 経由のみ(デバッグ可能なビルド)run-as 経由のみ
/sdcard/Android/data/<pkg>/files/パッケージ所有、スコープ付きストレージ所有パッケージからは可能。また、adb shell からも可能(FUSE/sdcardfs ブリッジ)
/data/local/tmp/

adb のバックエンドである shell ユーザーが読み取りと書き込みをできます。アプリの権限に関する手続きは不要です。プッシュした APK、テストデータ、キャプチャしたスクリーンショット、JSON 入力、dex 化したインストルメンテーション JAR など、すべてのステージングディレクトリとして使用します。

注意点:アプリは shell ではなく u0_aXX として実行されるため、chmod 0644 を設定し、親ディレクトリを全ユーザーが読み取れるようにしない限り、アプリは shell が書き込んだファイルを読み取れません。/data/local/tmp/ 自体は 0771 なので、アプリに読み取らせるファイルをプッシュするときは注意してください。

/sdcard/

/storage/emulated/0/、場合によっては /storage/self/primary/ のエイリアスです。パブリック/外部ストレージです。API 30 以降でも、ADB の shell ユーザーは FUSE/sdcardfs 経由でアクセスできるため、スコープストレージの制限が強化された後も、テストで adb push /sdcard/... と adb pull /sdcard/... を使用できます。API 30 以降では、アプリ側からの書き込みは MediaStore 経由に制限されます。

/data/data/<pkg>/ — run-as のみ

アプリの UID が所有しており、ディレクトリ自体の権限は 0700 です。一般的な本番用ユーザービルドでは、adb pull /data/data/com.example/... は失敗します。利用できる方法は次の 2 つです。

  • デバッグ可能なビルドでの run-as <pkg>(最も一般的です)。
  • userdebug/eng ビルドでの adb root(../../automation/scripting-adb-for-ci/SKILL.md で簡単に説明しています)。
/sdcard/Android/data/<pkg>/files/

アプリの「外部プライベート」ストレージで、アプリから Context.getExternalFilesDir(null) を使って書き込みます。FUSE エミュレート SD カード上にあるため、adb shell から読み取れます。Android 11 以降で「アーティファクトを書き込み、CI から取得する」場合に最も手軽なパスです。

# Inside the test app:
val out = File(context.getExternalFilesDir(null), "screenshots/fail.png")
# From CI:
adb pull /sdcard/Android/data/com.example/files/screenshots/fail.png ./

デバッグ可能なビルドでの run-as

run-as <pkg> は、Gingerbread 以降 Android に組み込まれている setuid ヘルパーです。APK のマージ済みマニフェストに android:debuggable="true" が指定されている場合に限り(debug ビルドバリアントではデフォルト)、shell の UID をアプリの UID に切り替えます。使用方法:adb shell run-as <pkg> <cmd>。

テキストファイルの取得
adb shell run-as com.example.app cat databases/main.db > main.db

これは短いテキストファイルには有効です。バイナリファイルでは、PTY 変換によってバイトが破損する可能性があります(下記の exec-out を参照)。

exec-out + tar によるバイナリをそのまま取得する方法

ディレクトリ、バイナリファイル、またはバイト列に \n が含まれるものを取得する場合は、必ず exec-out(バイナリをそのまま標準出力に出力)を使い、tar にパイプしてください。

adb exec-out run-as com.example.app tar cf - databases/ \
  | tar xf - -C ./pulled

tar cf - はアーカイブを標準出力に出力し、ローカルの tar xf - が ./pulled/ に展開します。中間ファイルは不要で、PTY 変換も行われず、任意のバイナリコンテンツに対応します。

単一のバイナリファイルの取得
adb exec-out run-as com.example.app cat databases/main.db > main.db

exec-out は標準出力をバイナリのまま保持します。

一覧表示
adb shell run-as com.example.app ls -lR files/
run-as でできないこと 3 つ
  1. リリースビルド/デバッグ不可のビルドでは動作しません。「Package is not debuggable」と表示されます。
  2. システム UID には切り替えられません(対象パッケージの UID のみです)。
  3. それだけではプロファイル境界を越えられません。必要に応じて --user N を使用してください。

テストアーティファクトでよく使われるパス

パス由来
app/build/outputs/connected_android_test_additional_output/<flavor><Variant>/connected/<deviceId>/Gradle の標準的な「追加出力」保存先です。インストルメンテーションランナーは、androidx.test:runner の引数 additionalTestOutputDir で指定されたものを取得します。
app/build/outputs/androidTest-results/connected/<flavor><Variant>/TEST-*.xmlJUnit 形式の XML テスト結果です。Jenkins/GHA などに直接取り込めます。
app/build/reports/androidTests/connected/HTML テストレポートです。

(developer.android.com/studio/test/command-line を参照:HTML は module/build/reports/androidTests/connected/、XML は module/build/outputs/androidTest-results/connected/ にあります。)

最新のアーティファクト API — TestStorage

従来の方法は「/sdcard/... に書き込み、CI スクリプトから取得する」です。最新の方法では、androidx.test.services.storage.TestStorage と useTestStorageService フラグを使用します。

設定
// dependencies
androidTestImplementation("androidx.test:runner:1.7.0")
androidTestUtil("androidx.test.services:test-services:1.6.0")
// am instrument args (or Gradle: testInstrumentationRunnerArguments)
adb shell am instrument -w -r \
  -e useTestStorageService true \
  com.example.test/androidx.test.runner.AndroidJUnitRunner
テスト内での使用方法
val storage = androidx.test.platform.io.PlatformTestStorageRegistry.getInstance()
storage.openOutputFile("screenshots/fail-${test.methodName}.png").use { out ->
    out.write(pngBytes)
}
TestStorageが解決すること
  • 出力ファイルは、/sdcard/そのものではなく、TestStorageService が管理するディレクトリに配置されます。
  • Gradle のconnectedAndroidTestが、それらを自動的にapp/build/outputs/connected_android_test_additional_output/に取得します。
  • テストごとのアーティファクトの名前空間化はランナーを通じて行われるため、Class.method形式のファイル名への手動変換は不要です。
  • Test Orchestrator のclearPackageData: 'true'と互換性があります。テスト間でテストアプリが消去されても、ストレージサービスがアーティファクトを保持します。

Gradle を使わないパイプラインでは、ストレージは同じ FS レイアウトを使用し、レガシー互換性のため/sdcard/googletest/test_outputfiles/...からアクセスできます。

パターン

パターン:誤り — デバッグ可能なビルドでのadb pull /data/data/<pkg>/...
# WRONG
adb pull /data/data/com.example/databases/main.db
# adb: error: failed to stat remote object '/data/data/com.example/databases/main.db': Permission denied
# WRONG because: /data/data/<pkg>/ is owned by the app UID with mode 0700. The shell user
# cannot traverse it. run-as solves this on debuggable builds.
# RIGHT — run-as + exec-out + cat for a binary-clean grab
adb exec-out run-as com.example cat databases/main.db > main.db
file main.db                 # → "SQLite 3.x database"

ディレクトリの場合:

adb exec-out run-as com.example tar cf - databases/ | tar xf - -C ./pulled
パターン:誤り — バイナリファイルに対するadb shell run-as <pkg> cat <binary>
# WRONG (on Windows / certain CI hosts)
adb shell run-as com.example cat databases/main.db > main.db
# WRONG because: `adb shell` allocates a PTY and translates LF to CRLF on stdout. SQLite
# headers and other binary content get corrupted. The pulled DB fails to open.
# RIGHT — exec-out is binary-clean
adb exec-out run-as com.example cat databases/main.db > main.db
パターン:アプリ内でアーティファクトを書き込み、CI から取得する
// In production / test code
val out = File(context.getExternalFilesDir(null), "captures/${name}.png")
out.outputStream().use { it.write(bitmapBytes) }
# CI script
adb pull /sdcard/Android/data/com.example/files/captures/ ./artifacts/

/sdcard/Android/data/<pkg>/files/はパッケージ専用の外部プライベートディレクトリであるため、API 30 以降ではrun-asなしで動作します。アプリとadb shell(FUSE ブリッジ)の両方から読み取れます。

パターン:大きなテストデータを高速で差分プッシュする
adb push --sync -z brotli ./fixtures/ /sdcard/fixtures/

--syncはタイムスタンプが一致するファイルをスキップし、-z brotliは転送データを圧縮します。変更されていないファイルが数百個あるフィクスチャディレクトリでは、2 回目のプッシュで転送は発生しません。

パターン:TestStorageを使った失敗時キャプチャ
class CaptureOnFailureRule : TestWatcher() {
    override fun failed(e: Throwable, description: Description) {
        val storage = PlatformTestStorageRegistry.getInstance()
        val name = "${description.className}.${description.methodName}"
        storage.openOutputFile("captures/$name.png").use { out ->
            out.write(captureScreenshotPng())
        }
    }
}

connectedAndroidTestの後、アーティファクトは自動的にapp/build/outputs/connected_android_test_additional_output/<flavor><Variant>/connected/<device>/captures/<name>.pngに配置されます。

必須ルール

  • 必須: バイナリファイルを取得するには、adb shell run-as ...ではなくadb exec-out run-as <pkg> cat <path>を使用します。PTY による変換は、Windows や一部の CI ホストでバイナリを破損させます。
  • 必須: /data/data/<pkg>/からディレクトリを取得するには、adb exec-out run-as <pkg> tar cf - <dir> | tar xf -を使用します。run-as経由で動作するadb pull相当の方法はありません。
  • 必須: run-asをスクリプトで使用する前に、ビルドがデバッグ可能であることを確認します。リリース APK では、run-asは「Package is not debuggable」というメッセージとともにゼロ以外の終了コードを返します。
  • 必須: API 30 以降では、アプリが書き込むテストアーティファクトには、任意の/sdcard/<dir>/よりも/sdcard/Android/data/<pkg>/files/(Context.getExternalFilesDir(null)経由)を優先します。後者へのアプリからの書き込みは MediaStore によって制限されます。
  • 必須: 新しいテストインフラストラクチャでは、独自の/sdcard/...パスよりもTestStorage(-e useTestStorageService true+androidx.test:services)を優先します。テストごとにアーティファクトを名前空間化し、Gradle のconnected_android_test_additional_output/出力先と統合されます。
  • 禁止: adb pull -p(進行状況を表示する)に依存しないでください。platform-tools 30 以降ではレガシーの何もしないオプションです。デフォルトの進行状況表示を使うか、表示を抑制するには-qを使用してください。
  • 禁止: /data/data/<pkg>/...に対するadb pullがrun-asなしで動作すると想定しないでください。デバッグ可能なビルドでも動作しません。pullはshellユーザーによる操作です。
  • 推奨: 大きなフィクスチャセットを繰り返しプッシュする場合は、--sync -z brotliを使用します。Brotli 圧縮とタイムスタンプによるスキップにより、速度の遅い USB 2.0 ケーブルでの転送が大幅に高速化されます。
  • 推奨: テストを分離してアーティファクトを保持するには、Test Orchestrator とclearPackageData: 'true'、useTestStorageService: 'true'を組み合わせて使用します。

検証

  • adb pull /data/data/<pkg>/... を直接実行するスクリプトはありません。このようなプルはすべて run-as 経由で行われます。
  • バイナリファイルのプルには adb shell ではなく adb exec-out を使用します。
  • アプリが書き込むすべてのテスト成果物は Context.getExternalFilesDir(...) を対象とします(/sdcard/Android/data/<pkg>/files/ に保存されます)。/sdcard/<arbitrary>/ は対象としません。
  • CI では、最新の成果物 API 用に app/build/outputs/connected_android_test_additional_output/**/* をアーカイブします。
  • 同じフィクスチャディレクトリを繰り返しプッシュする場合は、変更のないファイルをスキップできるよう adb push --sync を使用します。
  • file によって、プルしたすべての成果物のバイナリ形式が正しく報告されます(CRLF による破損がないこと)。
  • リリース APK に、/data/data/<pkg>/... から adb pull しようとするテストコードのパスはありません。そのような処理は失敗します。

参考資料

  • developer.android.com/tools/adb — pull/push の構文、「任意のディレクトリやファイルをデバイス上の任意の場所にコピーできる」という記述。
  • developer.android.com/studio/test/command-line — Gradle の出力ディレクトリ規則:HTML は module/build/reports/androidTests/connected/、XML は module/build/outputs/androidTest-results/connected/。
  • developer.android.com/training/data-storage/app-specific — Context.getExternalFilesDir() と API 30 以降のスコープ付きストレージのルール。
  • developer.android.com/reference/androidx/test/services/storage/TestStorage — 最新の成果物 API。
  • developer.android.com/reference/androidx/test/platform/io/PlatformTestStorageRegistry — ホスト側のハンドル。
  • リサーチノート tasks/research/A3-adb-observability-automation.md — プル/プッシュの全対象範囲、パスの権限ルール、run-as のパターン、Android 11 以降のスコープ付きストレージ、TestStorage の接続方法。
  • 関連スキル:../../architecture/understanding-adb-architecture/SKILL.md — サーバー/デーモン、exec-out と shell の違い。
  • 関連スキル:../../devices/connecting-to-devices/SKILL.md — adb get-state、複数デバイスの指定方法。
  • 関連スキル:../../devices/connecting-over-wifi/SKILL.md — ワイヤレス転送。
  • 関連スキル:../../apps/installing-and-managing-apps/SKILL.md — pm install と debuggable ビルドフラグ。
  • 関連スキル:../../tests/running-instrumented-tests-via-adb/SKILL.md — useTestStorageService を含む am instrument の引数。
  • 関連スキル:../../control/injecting-input-and-state/SKILL.md — 成果物を生成するシナリオの実行。
  • 関連スキル:../../capture/capturing-screenshots-and-screenrecord/SKILL.md — プルする PNG/MP4 の生成。
  • 関連スキル:../../observability/extracting-logs-with-logcat/SKILL.md — adb shell logcat -f と adb pull を使ったローテーション済みログファイルの取得。
  • 関連スキル:../../automation/scripting-adb-for-ci/SKILL.md — CI オーケストレーションの全体像。
  • 関連セット:../../../instrumentation/runner/running-instrumented-tests-with-androidjunit4/SKILL.md — TestStorage に接続された TestWatcher ルール。
  • 関連セット:../../../instrumentation/scenarios/launching-activities-with-activityscenario/SKILL.md — 成果物を生成する Activity 主導のシナリオ。
  • 関連セット:../../../fundamentals/strategies/applying-testing-strategies/SKILL.md — 成果物が適切な可観測性レイヤーとなる場合。

コメント