アイドリング リソースとは、その結果が後の UI テストの操作に影響を及ぼす非同期操作を指します。アイドリング リソースを Espresso に登録することにより、アプリのテスト時にこのような非同期操作をより確実に検証できます。
アイドリング リソースが必要になるケースの特定
Espresso には、洗練された同期機能のセットが用意されています。ただし、Espresso フレームワークのこの機能を適用できるのは、MessageQueue
へメッセージを送信する操作(画面にコンテンツを描画する View
のサブクラスなど)に対してのみです。
Espresso では、その他の非同期操作(バックグラウンド スレッドで実行されている操作を含む)は認識されないため、そのような操作との同期は保証されません。アプリ内の実行時間の長い操作が Espresso に認識されるようにするためには、それぞれの操作をアイドリング リソースとして登録する必要があります。
アプリの非同期操作の結果をテストする際にアイドリング リソースを使用しないと、テストの信頼性を向上させるために、次のような不適切な方法をとらざるをえなくなる可能性があります。
Thread.sleep()
の呼び出しの追加。テストに人為的な遅延を追加すると、テストスイートの実行完了までの時間が長くなるうえ、低速なデバイスではそれでもテストが失敗する可能性があります。また、アプリの将来のリリースでは非同期操作にさらに時間がかかるようになる可能性があり、このような遅延をそれに合わせて都度調整するのは困難です。- 再試行ラッパーの実装。再試行ラッパーとは、アプリが非同期操作を実施中かどうかの確認を、ループを使用してタイムアウトになるまで繰り返す方法です。テストで最大再試行回数を指定したとしても、再試行のたびにシステム リソース、特に CPU が消費されます。
CountDownLatch
インスタンスの使用。これにより、1 つ以上のスレッドを、別のスレッドで実行中の特定数の操作が完了するまで待機させることができます。ただし、これらのオブジェクトには、タイムアウトの時間を指定する必要があります。そうしないと、アプリが永久にブロックされる可能性があるからです。また、ラッチを使うことでコードが複雑になり、メンテナンスが困難になります。
Espresso では、アプリの非同期操作をアイドリング リソースとして登録することにより、このような信頼性に欠ける方法をテストで使用せずに済みます。
一般的なユースケース
テストで次の例と同様の操作を行う場合は、アイドリング リソースの使用を検討してください。
- インターネットやローカル データソースからのデータの読み込み。
- データベースやコールバックとの接続の確立。
- システム サービスまたは
IntentService
のインスタンスのいずれかを使用したサービスの管理。 - ビットマップ変換などの複雑なビジネス ロジックの実行。
このような操作によって UI が更新され、その後テストで検証される場合は、特にアイドリング リソースとして登録することが重要です。
アイドリング リソースの実装例
以下に、アプリと統合できるアイドリング リソースの実装例をいくつか紹介します。
CountingIdlingResource
- アクティブなタスクのカウンタを保持します。カウンタがゼロの場合、関連するリソースはアイドリング状態であると見なされます。この機能は、
Semaphore
の機能によく似ています。テスト中にアプリの非同期操作を管理するには、通常はこの実装で十分です。 UriIdlingResource
CountingIdlingResource
に似ていますが、リソースがアイドリング状態であると見なされるには、その前にカウンタが一定時間ゼロである必要があります。この待ち時間は、ネットワーク リクエストの連続を考慮したもので、スレッド内のアプリが前のリクエストのレスポンスを受け取った直後に新たなリクエストを出す場合を想定しています。IdlingThreadPoolExecutor
- 作成されたスレッドプール内で実行中のタスクの総数を保持する
ThreadPoolExecutor
のカスタム実装。このクラスでは、アクティブなタスクのカウンタの保持にCountingIdlingResource
が使用されます。 IdlingScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor
のカスタム実装。機能はIdlingThreadPoolExecutor
クラスと同じですが、将来予定されているタスクや定期的に実施される予定のタスクの数も保持できます。
独自のアイドリング リソースの作成
アプリのテストでアイドリング リソースを使用しながら、カスタム リソースの管理やログの記録が必要になる場合があります。それを行うには、前のセクションであげた実装では不十分かもしれません。その場合は、上記のアイドリング リソースの実装を拡張したり、独自のリソースを作成したりできます。
独自のアイドリング リソース機能を実装する場合は、以下の推奨事項(特に最初のもの)に留意してください。
- アイドル状態の確認の部分で、アイドル状態への遷移を呼び出さない。
onTransitionToIdle()
はisIdleNow()
の範囲には含めず、アプリがアイドル状態になった後に呼び出します。こうすれば、アイドル状態かどうかの確認を、Espresso が特定のアイドリング リソースに対して 2 回行うことはありません。
次のコード スニペットは、この推奨事項について示しています。
Kotlin
fun isIdle() { // DON'T call callback.onTransitionToIdle() here! } fun backgroundWorkDone() { // Background work finished. callback.onTransitionToIdle() // Good. Tells Espresso that the app is idle. // Don't do any post-processing work beyond this point. Espresso now // considers your app to be idle and moves on to the next test action. }
Java
public void isIdle() { // DON'T call callback.onTransitionToIdle() here! } public void backgroundWorkDone() { // Background work finished. callback.onTransitionToIdle() // Good. Tells Espresso that the app is idle. // Don't do any post-processing work beyond this point. Espresso now // considers your app to be idle and moves on to the next test action. }
- アイドリング リソースは必要になる前に登録する。
アイドリング リソースによる同期の効果は、Espresso でそのリソースの
isIdleNow()
メソッドを最初に呼び出した後に得られます。この性質の例を以下に示します。
@Before
アノテーションの付いたメソッドにアイドリング リソースを登録すると、各テストの最初の行でアイドリング リソースが有効になります。- テスト内でアイドリング リソースを登録すると、次回の Espresso ベースのアクションの間にアイドリング リソースが有効になります。アイドリング リソースを登録するステートメントと同じテスト内に次回のアクションがある場合でも、同様の動作となります。
- アイドリング リソース使用終了後は登録を解除する。
システム リソースを節約するため、アイドリング リソースが不要になったらすぐに登録を解除します。たとえば、アイドリング リソースを
@Before
アノテーションが付いたメソッドで登録する場合は、それに対応する@After
アノテーションが付いたメソッドで登録を解除するのが最適です。- アイドリング リソースの登録と登録解除にはアイドリング レジストリを使用する。
アプリのアイドリング リソース用にこのコンテナを使用することで、アイドリング リソースの登録と登録解除を必要に応じて繰り返しながら、一貫した動作をさせることができます。
- アイドリング リソース内では単純なアプリの状態のみを保持する。
たとえば、アイドリング リソースを実装して登録する場合は、そこに
View
オブジェクトへの参照は含めないようにします。
アイドリング リソースの登録
Espresso には、アプリのアイドリング リソースを登録するためのコンテナクラスが用意されています。このクラスは IdlingRegistry
と呼ばれ、アプリにかかるオーバーヘッドが極めて小さい自己完結型のアーティファクトです。このクラスを使用すると、次のようにしてアプリの保守性を高めることができます。
- アプリのテスト内で、そこに含まれるアイドリング リソースの代わりに
IdlingRegistry
への参照を作成します。 - 各ビルド バリアントで使用するアイドリング リソースのコレクション内での相違を維持します。
- アプリのサービスを参照する UI コンポーネントではなく、アプリのサービスでアイドリング リソースを定義します。
アイドリング リソースのアプリへの統合
アイドリング リソースをアプリに追加する方法はいくつかありますが、対象のアイドリング リソースが担う特定の操作を指定しながらアプリのカプセル化を維持できる方法は 1 つです。
推奨の方法
アプリにアイドリング リソースを追加する方法として、テストでは登録および登録解除の操作のみを行い、アイドリング リソースのロジック自体はアプリの本番用コードに配置することを強くおすすめします。
この方法は、本番用コードでテスト専用インターフェースを使用するという変則的なものですが、既存のコードにアイドリング リソースをラップでき、アプリの APK サイズとメソッド数を維持できるというメリットがあります。
その他の方法
アプリの製品版コードにアイドリング リソースのロジックを含めたくない場合は、他の統合方法として次のような選択肢があります。
- Gradle のプロダクト フレーバーなどのビルド バリアントを作成し、アプリのデバッグビルドでのみアイドリング リソースを使用します。
- Dagger などの依存関係の挿入フレームワークを使用して、アプリのアイドリング リソースの依存関係グラフをテストに挿入します。Dagger 2 を使用している場合は、挿入自体をサブコンポーネントから開始する必要があります。
アプリのテストにアイドリング リソースを実装し、テストと同期する必要があるアプリの実装部分を公開します。
注意事項: この設計方法では、アイドリング リソースへの自己完結型の参照を作成できるように見えますが、ごく単純なアプリ以外のカプセル化は維持できません。
参考情報
Espresso を使用した Android のテストに関するその他の情報については、次のリソースをご覧ください。
サンプル
- IdlingResourceSample: バックグラウンド ジョブとの同期。