DroidKaigi 2020 で発表する予定だった「Robolectricの限界を理解してUIテストを高速に実行しよう」のサンプルコードです。
本リポジトリは、 GoogleがAndroid Jetpackのサンプルアプリとして開発しているAndroid Sunflowerに、Robolectricで動作するEspressoで書かれたテストコードを追加したものです。
あわせて、本リポジトリには、EspressoのIdlingResourceをサポートするように改造したRobolectricが含まれています。 IdlingResource対応のRobolectricを試してみたい方は、 後述の「IdlingResource対応のRobolectricを試してみるには」を参照してください。
Android Sunflower付属のオリジナルのREADMEは、README.orig.mdを参照してください。
- Android Studio 3.5.3
- ライブラリの依存関係を最新化しています。
それに伴い、最新のライブラリでビルドできるように
PlantDetailFragment.kt
とMaskedCardView.kt
を修正しています。 - オリジナルのREADME.mdのファイル名をREADME.orig.mdにリネームしています。
- 既存のテストを削除しています。
- Espressoで書いたUIテストを
src/androidTest
とsrc/test
の両方に追加しています。 - Instrumented TestからもLocal Testからも参照できる
src/sharedTest
ディレクトリを追加しています。 - 植物が庭に追加されるときに、わざとバックグラウンドスレッドで3秒スリープするように変更しています。
また、そのときに実行されるスレッドをテストコードから変更できるようにしています。
(PlantDetailViewModel.kt
) - 一度保持した
AppDatabase
インスタンスを、テストコードから破棄できるようにしています。
(AppDatabase.kt
) - 一度保持したDAOインスタンスを、テストコードから差し替えられるようにしています。
(
GardenPlantingRepository.kt
とPlantRepository.kt
)
Espresso Test Recorderで記録したテスト(一部改変あり)を、
Instrumented Test
・Robolectricを使ったLocal Test
の両方で動作するようにしています。
テストの内容は次の通りで、Mango
を選ぶものとEggplant
を選ぶものの2つのケースが存在しています。
Add Plant
を押してPlant List
画面に遷移する- リストされている植物からを1つ選んで、FABを押して追加する
My Garden
画面に戻って、追加した植物が表示されていることを確認する。
テストのエントリーポイントは次の通りです。
- Instrumented Test:
src/androidTest/java/com/google/samples/apps/sunflower/GardenActivityTest2.kt
- Local Test:
src/test/java/com/google/samples/apps/sunflower/RobolectricGardenActivityTest2.kt
実際にEspressoのAPIを使って画面を操作している部分はPage Object化し、両方のテストから参照できる
src/sharedTest/java/com/google/samples/apps/sunflower/
{page,util}/
配下に配置しています。
本リポジトリには、EspressoのIdlingResourceをサポートするように改造したRobolectricが含まれています。 ご自身のプロジェクトに、IdlingResource対応のRobolectricを適用するには、次の手順にしたがってください。
次のディレクトリを、本リポジトリから、適用したいプロジェクトにコピーしてください。 コピー先も同じディレクトリ構成にしてください。
app/local-repo
app/src/test/resources
app/src/test/java/androidx
-
app/local-repo
を、mavenのリポジトリ参照先として追加してください// app/build.gradleに追記する場合 repositories { maven { url = file('local-repo') } }
-
依存関係に宣言されているRobolectricのバージョン表記を
4.3.1-modified
にしてくださいdependencies { ... testImplementation "org.robolectric:robolectric:4.3.1-modified" ... }
以上で、IdlingResourceに対応したRobolectricが使えるようになります。
- この対応は限られたケースで動作確認したに過ぎません。その点ご理解の上お試しください。
IdlingRegistry.registerLooperAsIdlingResource()
を使ったケースは未確認です。恐らく対応できていないと思います。
Robolectric 4.3から導入されたPAUSED
Looper Modeにしています。
Looper Modeについての詳細はImproving Robolectric's Looper simulationを参照してください。
@RunWith(AndroidJUnit4::class)
@LooperMode(LooperMode.Mode.PAUSED)
class RobolectricGardenActivityTest2 {
...
}
Robolectricで動かす場合、デフォルトのinitializerでは動作しません。
そのため、デフォルトのinitializerを削除し、WorkManagerTestInitHelper
を使って初期化しています。
デフォルトのinitializerを削除している箇所はsrc/test/AndroidManifest.xml
です。
初期化コードは、テスト専用のアプリケーションクラスTestApplication
を定義し、そのonCreate()
の中で実行しています。
Robolectricでは、テスト専用のアプリケーションクラスを次のように指定することができます。
@Config(application = TestApplication::class)
class RobolectricGardenActivityTest2 {
...
}
Robolectricの現バージョンでは、EspressoのIdling Resourceに対応していません。そのため、このサンプルでは独自実装によってRobolectricでIdling Resourceを待ち合わせるようにしてあります。
EspressoでIdling Resourceがアイドル状態になるのを待ち合わせている箇所はUiController
インターフェイスを実装したUiControllerImpl
です。
一方で、Robolectricが提供しているUiController
インターフェイスの実装はLocalUiController
で、こちらにはIdling Resourceを待ち合わせているコードがありません。
そこでLocalUiController
を拡張したIdlingLocalUiController
を実装し、RobolectricでもIdling Resourceを待ち合わせるようにしました。
具体的には、EspressoのUiControllerImpl
のうち、Idling Resourceを待ち合わせているロジックだけをIdlingLocalUiController
に移植しています。その差分はこのコミットを参照してください。
なお、Robolectricが提供するUiController
インターフェイスの実装は、JARファイルの
META-INF/services/androidx.test.platform.ui.UiController
にハードコードされているため、RobolectricのJARファイルにも手を加えざるを得ませんでした。
手を加えたRobolectricはapp/local-repo
ディレクトリ配下に格納しています。
オリジナルとの差分はMETA-INF/services/androidx.test.platform.ui.UiController
を削除した点のみです。
(後日、Robolectric本家にPRできればと考えています)
この対応のための修正は、 #2 にまとまっていますので、興味のある方は参考にしてみてください。
このIdlingResource対応は限られたケースで動作確認したに過ぎません。 前述の「Idling Resource対応についての注意事項」をご理解の上お試しください。
Robolectricでは、テスト独立性を高めるために、テスト終了時にデータベースファイルを削除する仕様となっています。
そのため、ビルドしたRoomDatabase
のインスタンスを、テストをまたがって保持する設計になっている場合、
2回目のテストからは存在しないデータベースファイルを参照することになり、データベースアクセスが正しく動作しません。
次のようにすることで、このRobolectricの仕様に対応することができます。 この対応にはアプリ側にも手を入れざるを得ないケースが多いと思います。
- Robolectric用に用意した
Application
クラスで、毎回RoomDatabase
をビルドするようにします。 - アプリケーション内で以下の参照を保持し続ける実装になっている場合は、
RoomDatabase
をビルドしたタイミングで、新しい参照に更新するようにします。- ビルドした
RoomDatabase
のインスタンス - DAOのインスタンス
- ビルドした
具体的な修正内容は #3 のうち、以下の箇所を参照してください。
AppDatabase.kt
GardenPlantingRepository.kt
PlantRepository.kt
RobolectricGardenActivityTest2.kt
のtearDown()
でappDatabase
のclose()
・clear()
を呼び出している箇所
次の機能は、Robolectricでは動作しないことが確認できています。 ここに挙げた機能を使った部分については、Robolectricによるテストを避けた方が無難です。
- Navigation Drawer内のメニュー操作
- Paging Libraryを使っているRecyclerViewの操作
- Espressoの
DrawerActions
API
Original Copyright 2018 Google, Inc. See README.orig.md for details.
Modifications Copyright 2020 TOYAMA Sumio <jun.nama@gmail.com>
Licensed under the Apache License, Version 2.0.
- Select text used for describing the plants (in
plants.json
) are used from Wikipedia via CC BY-SA 3.0 US (license inASSETS_LICENSE
). - "seed" by Aisyah is licensed under CC BY 3.0
robolectric-4.3.1-modified.jar
is a modified version of Robolectric 4.3.1 licensed under the Apache License, Version 2.0.IdlingLocalUiController.java
is a modified version ofUiControllerImpl.java
licensed under the Apache License, Version 2.0.PausedLooperInterrogator.java
is a modified version ofInterrogator.java
licensed under the Apache License, Version 2.0.TaskExecutorWithIdlingResourceRule.kt
is copied from GithubBrowserSample licensed under the Apache License, Version 2.0.DataBindingIdlingResource.kt
is modified version of Android Architecture Blueprints v2 and GithubBrowserSample licensed under the Apache License, Version 2.0.