モバイル アプリケーションとフレームワークの非同期性により、信頼性が高く再現可能なテストを作成することが困難な場合があります。ユーザー イベントが挿入されると、テスト フレームワークは、アプリがそのイベントへのレスポンスを完了するまで待機する必要があります。このレスポンスには、画面上のテキストの変更からアクティビティの完全な再作成まで、さまざまなものがあります。テストの動作が確定的でない場合は、不安定です。
Compose や Espresso などの最新のフレームワークはテストを念頭に設計されているため、次のテスト アクションまたはアサーションの前に UI がアイドル状態になることが保証されています。これが同期です。
同期をテストする
データベースからのデータの読み込みや無限アニメーションの表示など、テストで不明な非同期オペレーションやバックグラウンド オペレーションを実行すると、問題が発生する可能性があります。

テストスイートの信頼性を高めるには、Espresso アイドリング リソースなど、バックグラウンド オペレーションを追跡する方法を実装します。また、アイドル状態をクエリできるバージョンや同期を改善するバージョンのモジュールに置き換えることもできます。たとえば、コルーチンの TestDispatcher や RxJava の RxIdler などです。

安定性を高める方法
大規模なテストはアプリの複数のコンポーネントをテストするため、多くの回帰を同時に検出できます。通常はエミュレータまたはデバイスで実行されるため、忠実度が高くなります。大規模なエンドツーエンド テストは包括的なカバレッジを提供しますが、時折失敗する傾向があります。
不安定性を軽減するために講じることができる主な対策は次のとおりです。
- デバイスを正しく設定する
- 同期の問題を防ぐ
- 再試行を実装する
Compose または Espresso を使用して大規模なテストを作成するには、通常、いずれかのアクティビティを開始し、ユーザーが操作するように移動し、アサーションまたはスクリーンショット テストを使用して UI が正しく動作することを確認します。
UI Automator などの他のフレームワークでは、システム UI や他のアプリを操作できるため、より広範なスコープを許可できます。ただし、UI Automator テストでは手動での同期が必要になるため、信頼性が低くなる傾向があります。
デバイスを設定する
まず、テストの信頼性を高めるには、デバイスのオペレーティング システムがテストの実行を予期せず中断しないようにする必要があります。たとえば、システム アップデート ダイアログが他のアプリの上に表示されている場合や、ディスク容量が不足している場合などです。
デバイスファーム プロバイダがデバイスとエミュレータを設定するため、通常は操作する必要はありません。ただし、特別なケースでは独自の構成ディレクティブが使用される場合があります。
Gradle で管理されているデバイス
エミュレータを自分で管理する場合は、Gradle で管理されているデバイスを使用して、テストの実行に使用するデバイスを定義できます。
android {
testOptions {
managedDevices {
localDevices {
create("pixel2api30") {
// Use device profiles you typically see in Android Studio.
device = "Pixel 2"
// Use only API levels 27 and higher.
apiLevel = 30
// To include Google services, use "google".
systemImageSource = "aosp"
}
}
}
}
}
この構成では、次のコマンドでエミュレータ イメージを作成し、インスタンスを起動してテストを実行し、シャットダウンします。
./gradlew pixel2api30DebugAndroidTest
Gradle で管理されているデバイスには、デバイスの切断時などに再試行するメカニズムなど、さまざまな改善点が含まれています。
同期の問題を防ぐ
バックグラウンド オペレーションまたは非同期オペレーションを行うコンポーネントは、UI の準備が整う前にテスト文が実行されるため、テストが失敗する可能性があります。テストの範囲が広くなるほど、不安定になる可能性が高くなります。テスト フレームワークは、アクティビティの読み込みが完了したのか、もう少し待つ必要があるのかを推測する必要があるため、これらの同期の問題が不安定性の主な原因となります。
ソリューション
Espresso のアイドル状態のリソースを使用して、アプリがビジー状態になっていることを示すことはできますが、特に非常に大きなエンドツーエンド テストでは、すべての非同期オペレーションを追跡することは困難です。また、アイドル状態のリソースをインストールすると、テスト対象のコードが汚染される可能性があります。
アクティビティがビジーかどうかを推定する代わりに、特定の条件が満たされるまでテストを待機させることができます。たとえば、特定のテキストやコンポーネントが UI に表示されるまで待機できます。

Compose には、さまざまなマッチャーを待機するためのテスト API のコレクションが ComposeTestRule
の一部として用意されています。
fun waitUntilAtLeastOneExists(matcher: SemanticsMatcher, timeout: Long = 1000L)
fun waitUntilDoesNotExist(matcher: SemanticsMatcher, timeout: Long = 1000L)
fun waitUntilExactlyOneExists(matcher: SemanticsMatcher, timeout: Long = 1000L)
fun waitUntilNodeCount(matcher: SemanticsMatcher, count: Int, timeout: Long = 1000L)
ブール値を返す任意の関数を受け取る汎用 API もあります。
fun waitUntil(timeoutMillis: Long, condition: () -> Boolean): Unit
使用例:
composeTestRule.waitUntilExactlyOneExists(hasText("Continue")</code>)</p></td>
再試行メカニズム
不安定なテストは修正する必要がありますが、失敗する条件が非常にまれで、再現が難しい場合があります。不安定なテストは常に追跡して修正する必要がありますが、再試行メカニズムを使用すると、テストが成功するまで何度もテストを実行することで、デベロッパーの生産性を維持できます。
次のような問題を防ぐには、複数のレベルで再試行する必要があります。
- デバイスへの接続がタイムアウトした、または接続が切断された
- 単一のテスト失敗
再試行のインストールまたは構成は、テスト フレームワークとインフラストラクチャによって異なりますが、一般的なメカニズムには次のようなものがあります。
- テストを数回再試行する JUnit ルール
- CI ワークフローの再試行アクションまたはステップ
- Gradle で管理されているデバイスなど、エミュレータが応答しなくなったときにエミュレータを再起動するシステム。