Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DroidKaigi 2022: Gradle Managed Virtual Devicesで変化するエミュレータ活用術

TOYAMA Sumio
October 05, 2022

DroidKaigi 2022: Gradle Managed Virtual Devicesで変化するエミュレータ活用術

DroidKaigi 2022のセッション「Gradle Managed Virtual Devicesで変化するエミュレータ活用術」の発表資料です。

新しく導入されたGradle Managed Virtual Devices機能はどのようなものなのか、それによってエミュレータの使い方がどのように変化してくのか紹介します。
また、AGPの実装を調べて判明した内容を元に、Gradle Managed Virtual Devicesを活用する際に直面しがちなトラブルの解決方法も合わせて紹介します。

TOYAMA Sumio

October 05, 2022
Tweet

More Decks by TOYAMA Sumio

Other Decks in Programming

Transcript

  1. ⾃⼰紹介 p ⽒名: 外⼭ 純⽣ (TOYAMA Sumio) @sumio_tym (Twitter) /

    @sumio (GitHub) p 所属: DeNA SWET第⼆グループ (Software Engineer in Test) p 業務内容: 主にAndroidにおける 品質のボトルネック解決 p その他: 「Androidテスト全書」執筆 https://peaks.cc/sumio_tym/android_testing 2
  2. お話しすること Gradleでエミュレータ作成〜テスト実⾏を実現する Gradle Managed (Virtual) Devices (GMD)について説明します p GMDとはどんな機能? p

    GMDの⽅が便利なのはどんなケース? p GMDで実⾏に失敗したらどうデバッグする? 3 公式な⾔及では「Gradle Managed Virtual Devices」と「Gradle Managed Devices」 の呼称が混在していますが、以降ではAndroid Developersサイトで使われ始めた後者の 呼称を使います
  3. Agenda 1. GMD (Gradle Managed Devices)の使い⽅ 2. Androidエミュレータについておさらい 3. GMDの困るところに対応する

    4. テストの種類別GMD活⽤シーン 5. CIで動かすときのポイント 6. まとめ 4
  4. Agenda 5 1. GMD (Gradle Managed Devices)の使い⽅ 2. Androidエミュレータについておさらい 3.

    GMDの困るところに対応する 4. テストの種類別GMD活⽤シーン 5. CIで動かすときのポイント 6. まとめ
  5. GMD (Gradle Managed Devices)とは p AGP (Android Gradle Plugin)の新機能 p

    7.3.0よりstable (7.2では動かない機能あり) p build.gradleにエミュレータ種別を書いておけば、 AVD作成からテスト実⾏までGradleがやってくれる (AVD: Android Virtual Device) 6 AVD作成 エミュレータ起動 テスト実⾏ アプリ消去 エミュレータ終了 テストレポート作成
  6. (参考) バージョンの関係 p Android Studioも使う場合、Stable版以外ではバージョン 判定がシビアなので注意が必要 7 AGP Gradle (Android

    Studio) 7.2.2 7.3.3+ Chipmunk | 2021.2.1 Patch 2 7.3.0 7.4+ Dolphin | 2021.3.1 7.4.0-beta02 7.5+ Electric Eel | 2022.1.1 Beta 1 8.0.0-alpha02 7.5+ Flamingo | 2022.2.1 Canary 2 ※2022.10.04現在の、メジャーバージョンごとの最新版
  7. build.gradle (デバイスの定義) 8 android { testOptions { managedDevices { devices

    { pixel5api33 (ManagedVirtualDevice) { device = "Pixel 5" apiLevel = 33 systemImageSource = "google" require64Bit = false } ... }}}}
  8. build.gradle (デバイスの定義) 9 android { testOptions { managedDevices { devices

    { pixel5api33 (ManagedVirtualDevice) { device = "Pixel 5" apiLevel = 33 systemImageSource = "google" require64Bit = false } ... }}}} デバイス名(適当な名前をつける) Device Profile システムイメージ x86_64イメージを 強制的に使うかどうか
  9. systemImageSourceに指定できる値 10 従前のシステムイメージ ATD (Automated Test Device) (API Level 30,

    31のみ) 無印(AOSP) "aosp" "aosp-atd" Google APIs "google" "google-atd" Google Play "google_apis_playstore" (AGP 7.3+のみ) N/A ※ATD: Instrumented Test⽤途に最適化された軽量版システムイメージ。 ハードウェアレンダリングが無効化されていたり、 テストで使わなさそうなアプリやサービスが削除・無効化されている ※ATDで削除されているものの詳細は https://d.android.com/studio/test/gradle-managed-devices 参照
  10. build.gradle (複数個のデバイス定義) 11 android { testOptions { managedDevices { devices

    { pixel5api33 (ManagedVirtualDevice) { ... } pixel5api31 (ManagedVirtualDevice) { ... } pixel2api33 (ManagedVirtualDevice) { ... } } }}}
  11. android { testOptions { managedDevices { devices { pixel5api33 (ManagedVirtualDevice)

    { ... } pixel5api31 (ManagedVirtualDevice) { ... } pixel2api33 (ManagedVirtualDevice) { ... } } }}} build.gradle (複数個のデバイス定義) 12 複数個のデバイス定義を宣⾔できる
  12. build.gradle (デバイスグループ) 13 devices { pixel5api33 (ManagedVirtualDevice) { ... }

    pixel5api31 (ManagedVirtualDevice) { ... } pixel2api33 (ManagedVirtualDevice) { ... } } deviceGroups { pixel5 { targetDevices.add(devices.pixel5api33) targetDevices.add(devices.pixel5api31) } } }
  13. build.gradle (デバイスグループ) 14 devices { pixel5api33 (ManagedVirtualDevice) { ... }

    pixel5api31 (ManagedVirtualDevice) { ... } pixel2api33 (ManagedVirtualDevice) { ... } } deviceGroups { pixel5 { targetDevices.add(devices.pixel5api33) targetDevices.add(devices.pixel5api31) } } } デバイスグループ名 (適当な名前を付ける)
  14. 新しく作られるGradleタスク(基本的なもの) p pixel5api33DebugAndroidTest ( {デバイス名}{バリアント}AndroidTest ) p {デバイス名}に対応するエミュレータを起動し、 Instrumented Testを実⾏する

    p pixel5GroupDebugAndroidTest ( {デバイスグループ名}Group{バリアント}AndroidTest ) p {デバイスグループ名}の各デバイスについて、対応するエミュレー タを起動し、Instrumented Testを実⾏する 15
  15. 新しく作られるGradleタスク(その他①) p pixel5api33Setup ( {デバイス名}Setup ) p {デバイス名}に対応するAVDが無ければ新しく作り、 起動⾼速化のためにSnapshot(エミュレータのメモリなど)を保存する p

    {デバイス名}{バリアント}AndroidTestなどテスト実⾏時に呼ばれる p 時々このタスクだけ実⾏したくなることがある p cleanManagedDevices p この仕組みで作られたAVDを全部消す p 実⾏注意!! 16
  16. AVDの保存場所 GMDが作るAVDは隠されている p Android StudioのDevice Managerで作るAVD: $HOME/.android/avd/ p GMDが作るAVD (AGP

    7.3+): $HOME/.android/avd/gradle-managed p GMDが作るAVD (AGP 7.2.x): $HOME/.android/gradle/avd/ 19
  17. GMDの困るところ p テスト実⾏中の画⾯が⾒えない p アプリが起動しているかどうかすら分からない p AVD作成時のカスタマイズ項⽬が貧弱 p せめて⽇本語の環境で動かしたい p

    RAMサイズ、ストレージサイズくらいは変更したい! p テストが終わるとapkがアンインストールされる p テスト実⾏中に作られたファイルも消えてしまう・・ p テストに失敗したときのデバッグが難しい 22
  18. Agenda 24 1. GMD (Gradle Managed Devices)の使い⽅ 2. Androidエミュレータについておさらい 3.

    GMDの困るところに対応する 4. テストの種類別GMD活⽤シーン 5. CIで動かすときのポイント 6. まとめ
  19. CLIでエミュレータを起動する② p AVD名⼀覧表⽰ emulator -list-avds p AVD名を指定してエミュレータ起動 emulator -avd {AVD名}

    27 この⽅法で表⽰・起動できるのは Android Studioで作ったAVDのみ ($HOME/.android/avd/に保存されているもの)
  20. AVDのファイル構成 (主要なもののみ) 29 ├── Pixel_3a_API_29.avd/ │ ├── hardware-qemu.ini │ ├──

    config.ini │ ├── encryptionkey.img, encryptionkey.img.qcow2 │ ├── cache.img, cache.img.qcow2 │ ├── userdata-qemu.img, userdata-qemu.img.qcow2 │ ├── userdata.img │ ├── ... └── Pixel_3a_API_29.ini ハードウェア・AVDの設定 ディスクイメージ
  21. Agenda 31 1. GMD (Gradle Managed Devices)の使い⽅ 2. Androidエミュレータについておさらい 3.

    GMDの困るところに対応する 4. テストの種類別GMD活⽤シーン 5. CIで動かすときのポイント 6. まとめ
  22. GMDの困るところ (再掲) 32 p テスト実⾏中の画⾯が⾒えない p アプリが起動しているかどうかすら分からない p AVD作成時のカスタマイズ項⽬が貧弱 p

    せめて⽇本語の環境で動かしたい p RAMサイズ、ストレージサイズくらいは変更したい! p テストが終わるとapkがアンインストールされる p テスト実⾏中に作られたファイルも消えてしまう・・ p テストに失敗したときのデバッグが難しい
  23. 別アプリの助けを借りて⾔語設定を変更する ① io.appium.settingsを使う(https://github.com/appium/io.appium.settings) p インストール(npm) npm i io.appium.settings p app/build.gradle

    36 dependencies { androidTestUtil files('../node_modules/io.appium.settings/apks/settings _apk-debug.apk') } テスト実⾏時に、指定されたapkを パーミッションをgrantした状態で インストールしてくれる
  24. 別アプリの助けを借りて⾔語設定を変更する ② 37 class LocaleChangeListener : RunListener() { override fun

    testRunStarted(desc: Description?) { val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation val fd = uiAutomation.executeShellCommand("...") // 標準出⼒を全部捨てる }} p JUnit4のRunListenerを使って最初のテスト開始前に以下を実⾏する am broadcast -a io.appium.settings.locale -n io.appium.settings/.receivers.LocaleSettingReceiver --es lang {⾔語} --es country {地域}
  25. 別アプリの助けを借りて⾔語設定を変更する ② 38 class LocaleChangeListener : RunListener() { override fun

    testRunStarted(desc: Description?) { val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation val fd = uiAutomation.executeShellCommand("...") // 標準出⼒を全部捨てる }} p JUnit4のRunListenerを使って最初のテスト開始前に以下を実⾏する am broadcast -a io.appium.settings.locale -n io.appium.settings/.receivers.LocaleSettingReceiver --es lang {⾔語} --es country {地域}
  26. 別アプリの助けを借りて⾔語設定を変更する ② 39 class LocaleChangeListener : RunListener() { override fun

    testRunStarted(desc: Description?) { val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation val fd = uiAutomation.executeShellCommand("...") // 標準出⼒を全部捨てる }} テストコードから シェルコマンドを実⾏できる p JUnit4のRunListenerを使って最初のテスト開始前に以下を実⾏する am broadcast -a io.appium.settings.locale -n io.appium.settings/.receivers.LocaleSettingReceiver --es lang {⾔語} --es country {地域}
  27. 別アプリの助けを借りて⾔語設定を変更する ③ p build.gradleでRunListener実装を登録する 40 android { defaultConfig { testInstrumentationRunnerArgument(

    "listener", "com.example.gmd.LocaleChangeListener" ) } } • RunListener実装のクラス名を書く • 複数登録する場合はコンマ(,)区切り
  28. GMDで作られたAVDの設定を変える p GMDで作られたAVDのconfig.iniなどを変更する 場所: $HOME/.android/avd/gradle-managed/{AVD名}.avd/ p ディスクサイズ変更: disk.dataPartition.size=6g (config.ini) p

    etc. p 何を変更すれば良いか知りたいとき p hardware-properties.iniに詳しく定義されている https://bit.ly/3SXgisv (android.googlesource.com) 42
  29. テスト終了時にファイルを取り出す① p 隠しオプションadditionalTestOutputDirを使う 44 android { defaultConfig { testInstrumentationRunnerArgument( "additionalTestOutputDir",

    "/sdcard/Android/media/com.example.gmd/result" ) } } ファイルを取り出したい (エミュレータ上の)ディレクトリ
  30. テスト終了時にファイルを取り出す② p 取り出された(pullされた)ファイルの保存先 p AGP 8.0未満: app/build/outputs/ managed_device_android_test_additional_output p AGP

    8.0以上: app/build/intermediates/ managed_device_android_test_additional_output p 引数に指定できるディレクトリについての注意 p Scoped Storageが有効なOSでは /sdcard/Android/media/{アプリケーションID}/ から始まるディレクトリしか受け付けない (それ以外に保存されたファイルは取り出せない) 45
  31. エミュレータを起動した状態でテストを動かす① p 予めGMDが作成したAVDでエミュレータを起動しておく env ANDROID_AVD_HOME=$HOME/.android/avd/gradle-managed emulator –avd {AVD名} p より正確に再現したいときは、以下のファイルを参考に

    emulatorコマンドのオプションを付ける (GMDでエミュレータを起動したときの環境変数や コマンドラインオプションなどが記録されている) app/build/outputs/androidTest-results/ managedDevice/(省略)/emulator.1.ok.txt 47
  32. (参考) emulator.1.ok.txtの例 48 EXECUTING: /usr/local/Caskroom/android- sdk/4333796/emulator/emulator @dev31_default_x86_64_Pixel_5 - no-window -no-audio

    -gpu auto-no-window -read-only -no-boot- anim -id :app:pixel5api31aospDebugAndroidTest CURRENT_WORKING_DIRECTORY: ... START_TIME: ... ENVIRONMENT: ... ANDROID_AVD_HOME=/Users/sumio.toyama/.android/avd/gradle- managed ***************************************** STDOUT/STDERR BELOW
  33. その他のTips: テストの録画(テスト全体を1ファイルに録画) p JUnit4のRunListenerを使って、初回のテスト開始前に screenrecordコマンドを実⾏する p 標準出⼒をreadしなければコマンド終了を待たずに先に進める p 全テスト終了時(RunListener.testRunFinished())に 実⾏中のscreenrecordコマンドをkillする

    p "pidof screenrecord" でpidを探す p 探したpidに対して"kill –INT $pid" p 録画したmp4はadditionalTestOutputDirで取り出す 参考: JUnit Test Ruleとしての実装サンプル ScreenRecordRule.kt https://gist.github.com/mkeeda/30b8cfdcec53859a2cd39cac36d7fc9e 50
  34. その他のTips: そのほか② p (デバイスグループなど)複数のAVDをまとめてテストする と結果が不安定になる問題を解決したい p GMDインスタンス数の並列度を下げる(AGP 7.4+) -Pandroid.experimental.testOptions.managedDevices .maxConcurrentDevices=1

    p Gradleタスクの並列度を下げる -Dorg.gradle.workers.max=1 p 事前にpackageDebugAndroidTestで並列にビルドしておき、 テストだけ本オプションを付けるのがおすすめ 52
  35. ここまでのまとめ p 以下の⼿段で、困りごとは概ね解決できる p --enable-displayオプション p config.iniのカスタマイズ p ANDROID_AVD_HOME環境変数を指定した エミュレータ起動

    p JUnit4のRunListenerで最初のテスト開始前にシェルコマンド発⾏ p エミュレータ起動時のオプションを変更したり、 テスト実⾏前後に任意の処理を挟み込むのは難しい 54
  36. Agenda 55 1. GMD (Gradle Managed Devices)の使い⽅ 2. Androidエミュレータについておさらい 3.

    GMDの困るところに対応する 4. テストの種類別GMD活⽤シーン 5. CIで動かすときのポイント 6. まとめ
  37. Robolectricが必要なLocal Testの移⾏① エミュレータ(GMD)で動かせばテスト忠実度は確実に上がる p Roblectricのバージョンアップ対応⼯数をゼロにできる p GMDへの移⾏は⽐較的容易なはず p src/test から

    src/androidTestへ移動 (src/sharedTest はChipmunkから使えなくなった) p Robolectric固有のアノテーション(@Configなど)の削除 p AVDのカスタマイズが必要なケースは少ないはず p 軽量なATD (Automated Test Device)でも動作するはず 57
  38. Robolectricが必要なLocal Testの移⾏② p GMD (ATD)に移⾏した場合のテスト実⾏時間は? p 1テストクラス、18テストケースで計測 p iMac Pro

    2017 3.2GHz Xeon 8コア、32GB RAM 58 Robolectric GMD (ATD) テストクラス初回ロード 6〜15sec 0.6〜0.7sec エミュレータ起動 0sec 40〜60sec テスト実⾏時間 (初回ロード以外) 0.900〜1.000sec 0.200〜0.300sec
  39. UIテスト p AVDのカスタマイズ無しに動くテストはGMDで p 環境差異による不安定なテストを減らせる p 軽量版でない⽅のGMDを使うのが無難 p 軽量のATD (Automated

    Test Device)では画⾯表⽰不可 p UIテストで使えるTips p 画⾯を⾒ながら動かす: --enable-displayオプション p ⾔語設定を英語以外にしたい: io.appium.settingsアプリ 60
  40. スクリーンショットテスト p 判断基準やTipsはUIテストと同じ p 軽量版でない⽅のGMDが無難 p ATDで使えるスクリーンショットはCanvasにView.draw()する⽅式のみ p スクリーンショット保存先に注意 p

    /sdcard/Android/media/{アプリケーションID}/ から始まるディレクトリに保存する p 保存したスクリーンショットはadditionalTestOutputDir オプションを使って取り出す 61
  41. Agenda 63 1. GMD (Gradle Managed Devices)の使い⽅ 2. Androidエミュレータについておさらい 3.

    GMDの困るところに対応する 4. テストの種類別GMD活⽤シーン 5. CIで動かすときのポイント 6. まとめ
  42. CI環境でGMDを使う前に意識したいこと 既に⽤意されているStep・Actionなどは GMDよりカスタマイズできる項⽬が多い p カスタマイズが必要なら既存の⽅法にとどまった⽅が楽 p Bitrise: AVD Manager Step・Wait

    for Android emulator Step https://devcenter.bitrise.io/en/steps-and-workflows/workflow-recipes-for-android-apps/- android--run-tests-using-the-emulator.html p CircleCI: Android Orbの android/start-emulator-and-run-tests Step https://circleci.com/docs/ja/android-machine-image p GitHub Actions: malinskiy/action-android/emulator-run-cmd https://github.com/Malinskiy/action-android p Jenkins: Android Emulator Plugin https://github.com/jenkinsci/android-emulator-plugin 64
  43. ポイント1: エミュレータが動作するか確認する p 仮想環境ではNested Virtualizationが必要 p CircleCIではマシンイメージを使う(Dockerイメージ不可) p エミュレータが動く潤沢なRAMが必要 p

    Gradleの消費メモリも考えると8GBでは⾜りない p 既存のエミュレータ利⽤に関する設定例を参考にする p Bitrise: 情報なし(⼿許ではAndroid & Docker, on Ubuntu 20.04で動作) p CircleCI: androidマシンイメージ (Linux Large) p GitHub Actions: macOS-10.15 65
  44. ポイント3: テスト失敗時の解析をやりやすくする p ログファイルをArtifactsに保存しておく app/build/outputs/androidTest-results/** p テストを録画し、動画ファイルをArtifactsに保存しておく (「その他のTips: テストの録画」参照) p

    実⾏時ログをより詳しくするGradleオプションを付ける p --info p -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true 67 (参考) SunflowerアプリのGitHub Actions設定 https://github.com/android/sunflower/pull/785
  45. GMDでAVDをカスタマイズしたいとき③ p ディスクイメージのカスタマイズは⼒技が必要 1. ⼿許のマシンで{デバイス名}SetupタスクでAVDを作る 2. 作ったAVDでエミュレータ起動・キッティング(Apple Siliconは未確認) 3. AVD内の以下のディスクイメージをGitHubリポジトリなどに保存

    (数GBになるので注意。LFS推奨) p userdata-qemu.img.qcow2 p encryptionkey.img.qcow2 p 状況によっては他のイメージファイルも必要かも知れない 4. config.ini書き換えの要領でディスクイメージを差し替える 70
  46. ここまでのまとめ p 本当にGMD使った⽅が良いか考える p AVDのカスタマイズが必要なら敢えてとどまる選択肢も p ポイントは3つ p エミュレータが起動できる環境を⽤意する p

    必要に応じてGradleオプションを付ける p テスト失敗時の解析をやりやすくする p GMDでAVDをカスタマイズする場合は程々に・・ 71
  47. Agenda 72 1. GMD (Gradle Managed Devices)の使い⽅ 2. Androidエミュレータについておさらい 3.

    GMDの困るところに対応する 4. テストの種類別GMD活⽤シーン 5. CIで動かすときのポイント 6. まとめ
  48. p Gradle Managed Device (GMD) の設定⽅法、使い ⽅、⾜りない点を補う⽅法について説明しました p この内容でかなりのことが出来るようになるはずです p

    テストの種類別にGMDを使うと便利なケース、 そうでないケースを説明しました p GMD活⽤の前提となる CIで動かすときのポイントもあわせて紹介しました 73
  49. 参考URL① p 公式ドキュメント https://d.android.com/studio/test/gradle-managed-devices p Android Code Search (Gradleのオプションが定義されている場所) https://bit.ly/3rrFZp8

    (cs.android.com) p Android Sunflower with Compose (GitHub Actions設定例) https://github.com/android/sunflower p Appiumのio.appium.settingsアプリ https://github.com/appium/io.appium.settings p GitHub Gist: mkeeda/ScreenRecordRule.kt https://gist.github.com/mkeeda/30b8cfdcec53859a2cd39cac36d7fc9e 74
  50. 参考URL② p tkmnzm 「Androidのテストで利⽤できるスクリーンショット取得APIのまとめ」 https://qiita.com/tkmnzm/items/c25c43a8bac07bb90dfb p Bitrise「(Android) Run tests using

    the emulator」 https://bit.ly/3Csu3K5 (devcenter.bitrise.io) p CircleCI「AndroidイメージのMachine Executorでの使⽤」 https://circleci.com/docs/ja/android-machine-image p GitHub Actions: action-android https://github.com/Malinskiy/action-android p Android Emulator Plugin for Jenkins https://github.com/jenkinsci/android-emulator-plugin 75