Espresso アイドリング リソース

アイドリング リソースは非同期処理を表します。この結果は UI テストの後続の処理に影響します。Espresso にアイドリング リソースを登録することで、アプリのテスト時にこれらの非同期オペレーションをより確実に検証できます。

アイドリング リソースが必要になるケースの特定

Espresso には、高度な同期機能セットが用意されています。ただし、このフレームワークの特性は、MessageQueue にメッセージを投稿するオペレーション(画面にコンテンツを描画する View のサブクラスなど)にのみ適用されます。

Espresso は、他の非同期オペレーション(バックグラウンド スレッドで実行されているオペレーションを含む)を認識しないため、そのような状況での同期を保証できません。アプリの長時間実行オペレーションを Espresso に認識させるには、各オペレーションをアイドリング リソースとして登録する必要があります。

アプリの非同期処理の結果をテストする際にアイドリング リソースを使用しないと、テストの信頼性を向上させるために、次のいずれかの回避策を講じなければならない場合があります。

  • Thread.sleep() の呼び出しの追加。テストに人為的な遅延を追加すると、テストスイートの実行が完了するまでに時間がかかり、低速なデバイスでテストを実行すると、テストが失敗することがあります。さらに、アプリが将来のリリースでは時間のかかる非同期処理を実行しなければならない可能性があるため、こうした遅延はスケーラビリティに優れません。
  • 再試行ラッパーの実装: タイムアウトが発生するまでアプリが非同期処理を実行しているかどうかをループによって繰り返し確認します。テストで最大再試行回数を指定しても、再実行のたびにシステム リソース(特に CPU)が消費されます。
  • CountDownLatch のインスタンスを使用。これにより、別のスレッドで実行中の特定の数のオペレーションが完了するまで、1 つ以上のスレッドを待機させることができます。これらのオブジェクトの場合、タイムアウト時間を指定する必要があります。指定しないと、アプリが無期限にブロックされる可能性があります。また、ラッチによってコードが不必要に複雑になり、メンテナンスが困難になります。

Espresso を使用すると、こうした信頼性の低い回避策をテストから削除し、代わりにアプリの非同期作業をアイドリング リソースとして登録できます。

一般的なユースケース

テストで次の例のようなオペレーションを実行する場合は、アイドリング リソースの使用を検討してください。

  • インターネットやローカル データソースからのデータの読み込み
  • データベースやコールバックとの接続の確立
  • システム サービスまたは IntentService のインスタンスを使用してサービスを管理する
  • ビットマップ変換などの複雑なビジネス ロジックの実行

テストで検証する UI がこれらのオペレーションによって更新される場合は、アイドリング リソースを登録することが特に重要です。

アイドリング リソースの実装例

次のリストでは、アプリに統合できるアイドリング リソースの実装例をいくつか示します。

CountingIdlingResource
アクティブなタスクのカウンタを保持します。カウンタがゼロの場合、関連リソースはアイドル状態とみなされます。この機能は、Semaphore の機能によく似ています。ほとんどの場合、テスト中のアプリの非同期処理を管理するには、この実装で十分です。
UriIdlingResource
CountingIdlingResource に似ていますが、リソースがアイドル状態とみなされるには、一定の時間カウンタがゼロである必要があります。この追加の待機時間は、連続するネットワーク リクエストを考慮したもので、スレッド内のアプリが前のリクエストに対するレスポンスを受け取った直後に新しいリクエストを行う場合があります。
IdlingThreadPoolExecutor
作成されたスレッドプール内で実行中のタスクの合計数を追跡する ThreadPoolExecutor のカスタム実装。このクラスは CountingIdlingResource を使用して、アクティブなタスクのカウンタを維持します。
IdlingScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor のカスタム実装。機能性は IdlingThreadPoolExecutor クラスと同じですが、将来予定されているタスクや、定期的に実行するようスケジュールされているタスクを追跡することもできます。

独自のアイドリング リソースの作成

アプリのテストでアイドリング リソースを使用する場合は、カスタム リソース管理やロギングを提供する必要があります。そのような場合、前のセクションに記載されている実装では不十分な可能性があります。この場合は、これらのアイドリング リソース実装のいずれかを拡張するか、独自のリソース実装を作成できます。

独自のアイドリング リソース機能を実装する場合は、次のベスト プラクティス、特に最初のベスト プラクティスに留意してください。

アイドル状態の確認の部分で、アイドル状態への遷移を呼び出さない。
アプリがアイドル状態になったら、isIdleNow() の実装の外部で onTransitionToIdle() を呼び出します。これにより、Espresso は、特定のアイドリング リソースがアイドル状態かどうかの不要なチェックを再度実行しません。

次のコード スニペットは、この推奨事項について示しています。

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 コンポーネントではなく、そのサービスで定義します。

アイドリング リソースのアプリへの統合

アイドリング リソースをアプリに追加する方法はいくつかありますが、特に一つのアプローチでは、アプリのカプセル化を維持しつつ、特定のアイドリング リソースが表す特定のオペレーションを指定できます。

アプリにアイドリング リソースを追加する場合は、アプリ自体にアイドリング リソースのロジックを配置し、テストでは登録操作と登録解除操作のみを実行することを強くおすすめします。

この方法を採用すると、本番環境のコードでテスト専用のインターフェースを使用するという通常とは異なる状況になりますが、アプリの APK サイズとメソッド数を維持しながら、既存のコードにアイドリング リソースをラップできます。

その他の方法

アプリの本番環境のコードにアイドリング リソースのロジックを含めたくない場合は、他にも次のような統合戦略があります。

  • Gradle のプロダクト フレーバーなどのビルド バリアントを作成し、アプリのデバッグビルドでのみアイドリング リソースを使用します。
  • Dagger のような依存関係注入フレームワークを使用して、アプリのアイドリング リソース依存関係グラフをテストに挿入します。Dagger 2 を使用している場合は、インジェクション自体はサブコンポーネントから行う必要があります。
  • アプリのテストにアイドリング リソースを実装し、それらのテストで同期する必要があるアプリの実装の部分を公開します。

    注意: この設計上の決定は、アイドリング リソースへの自己完結型の参照を作成するように見えますが、最もシンプルなアプリ以外のすべてのカプセル化を破ります。

参考情報

Android のテストで Espresso を使用する方法について詳しくは、以下のリソースをご覧ください。

サンプル