Android アプリのテストの基礎

このページでは、Android アプリのテストに関する基本原則(主なベスト プラクティスとそのメリットなど)について概説します。

テストのメリット

アプリの開発において、テストは非常に重要なプロセスです。アプリに対して一貫性のあるテストを実施することで、アプリの公開前に、その正確性、機能の動作、使いやすさを検証できます。

アプリを操作して手動でテストできます。さまざまなデバイスやエミュレータを使用し、システム言語を変更して、すべてのユーザーエラーを生成したり、すべてのユーザーフローを走査したりします。

ただし、手動テストはスケールが悪く、アプリの動作の回帰を見落とす可能性があります。自動テストでは、テストを実行するツールを使用します。このツールは高速で再現性が高く、通常は開発プロセスの早い段階でアプリに関する実用的なフィードバックを得ることができます。

Android のテストタイプ

モバイル アプリケーションは複雑であり、さまざまな環境で適切に動作する必要があります。そのため、テストにはさまざまな種類があります。

対象

たとえば、subject に応じて異なる種類のテストがあります。

  • 機能テスト: アプリは想定どおりに動作しますか?
  • パフォーマンス テスト: 迅速かつ効率的に実行されますか?
  • ユーザー補助機能のテスト: ユーザー補助サービスと連携して動作しますか?
  • 互換性テスト: すべてのデバイスと API レベルで適切に動作しますか?

範囲

テストは、サイズ分離の程度によっても異なります。

  • 単体テストまたは小規模テストでは、メソッドやクラスなど、アプリのごく一部のみを検証します。
  • エンドツーエンド テストまたは大規模テストでは、画面全体やユーザーフローなど、アプリの広範な部分を同時に検証します。
  • 中規模テストは、2 つ以上のユニット間の統合を確認します。
テストは、小規模、中規模、大規模のいずれかにできます。
図 1: 一般的なアプリのテストスコープ。

テストを分類する方法はたくさんあります。ただし、アプリ デベロッパーにとって最も重要な違いは、テストの実行場所です。

インストルメンテーション テストとローカルテスト

テストは Android デバイスまたは別のパソコンで実行できます。

  • インストルメンテーション テストは、物理デバイスまたはエミュレートされた Android デバイスで実行されます。アプリは、コマンドを挿入して状態を読み取るテストアプリとともにビルドされ、インストールされます。インストルメンテーション テストは通常、アプリを起動して操作する UI テストです。
  • ローカルテストは開発マシンまたはサーバーで実行されるため、ホストサイド テストとも呼ばれます。通常は小規模で高速であり、テスト対象をアプリの他の部分から分離します。
テストは、デバイス上でインストルメンテーション テストとして実行することも、開発マシン上でローカルテストとして実行することもできます。
図 2: 実行場所に応じたさまざまなタイプのテスト。

すべての単体テストがローカルであるわけではなく、すべてのエンドツーエンド テストがデバイス上で実行されるわけでもありません。次に例を示します。

  • 大規模なローカルテスト: Robolectric など、ローカルで実行される Android シミュレータを使用できます。
  • 小規模なインストルメンテーション テスト: コードが SQLite データベースなどのフレームワーク機能と適切に動作することを確認できます。このテストを複数のデバイスで実行して、複数バージョンの SQLite との統合を確認できます。

次のスニペットは、要素をクリックして別の要素が表示されることを確認するインストルメンテーション UI テストで UI を操作する方法を示しています。

Espresso

// When the Continue button is clicked
onView(withText("Continue"))
    .perform(click())

// Then the Welcome screen is displayed
onView(withText("Welcome"))
    .check(matches(isDisplayed()))

Compose UI

// When the Continue button is clicked
composeTestRule.onNodeWithText("Continue").performClick()

// Then the Welcome screen is displayed
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()

次のスニペットは、ViewModel の単体テスト(ローカル、ホスト側のテスト)の一部を示しています。

// Given an instance of MyViewModel
val viewModel = MyViewModel(myFakeDataRepository)

// When data is loaded
viewModel.loadData()

// Then it should be exposing data
assertTrue(viewModel.data != null)

テスト可能なアーキテクチャ

テスト可能なアプリ アーキテクチャでは、コードが構造に従っているため、コードのさまざまな部分を個別に簡単にテストできます。テスト可能なアーキテクチャには、読みやすさ、メンテナンス性、スケーラビリティ、再利用性などの他の利点もあります。

テスト不可能なアーキテクチャの場合、次のようになります。

  • テストの規模が大きくなり、速度が低下し、不安定になります。単体テストできないクラスは、大規模な統合テストまたは UI テストでカバーする必要があります。
  • さまざまなシナリオをテストする機会が少ない。テストが長くなると実行時間が長くなるため、アプリのすべての状態をテストすることは現実的ではありません。

アーキテクチャ ガイドラインの詳細については、アプリ アーキテクチャ ガイドをご覧ください。

分離へのアプローチ

関数、クラス、またはモジュールの一部を残りの部分から抽出できると、テストは簡単で、より効率的になります。この方法は分離と呼ばれ、テスト可能なアーキテクチャにとって最も重要なコンセプトです。

一般的な分離手法には次のようなものがあります。

  • アプリをプレゼンテーション、ドメイン、データなどのレイヤに分割します。アプリを機能ごとに 1 つずつモジュールに分割することもできます。
  • アクティビティやフラグメントなど、依存関係が大きいエンティティにロジックを追加しないでください。これらのクラスをフレームワークへのエントリ ポイントとして使用し、UI とビジネス ロジックを、Compose、ViewModel、ドメインレイヤなど、他の場所に移動します。
  • ビジネス ロジックを含むクラスで、直接的なフレームワーク依存関係は避けてください。たとえば、ViewModel で Android コンテキストを使用しないでください
  • 依存関係を簡単に置き換えできるようにします。たとえば、具体的な実装ではなくインターフェースを使用します。DI フレームワークを使用していない場合でも、依存性注入を使用してください。

次のステップ

テストが必要な理由と、主な 2 種類のテストについて理解できたところで、テストする内容を確認するか、テスト戦略について学びましょう。

また、最初のテストを作成して実践的に学習する場合は、テスト Codelab をご覧ください。