Android アプリのテストの基礎

このページでは、Android アプリのテストの基本原則について説明します。中心となるベスト プラクティスとそのメリットについても解説します。

テストのメリット

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

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

ただし、手動テストはスケーリングが難しく、アプリの動作の回帰を見落としやすいことがあります。自動テスト では、テストを実行するツールを使用します。これにより、開発プロセスの早い段階で、より迅速かつ反復可能で、アプリに関する実用的なフィードバックを得ることができます。

Android のテストの種類

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

件名

たとえば、対象によってテストの種類が異なります。

  • 機能テスト: アプリが想定どおりに動作するかどうか。
  • パフォーマンス テスト: 迅速かつ効率的に動作するかどうか。
  • ユーザー補助機能のテスト: ユーザー補助サービスで適切に動作するかどうか。
  • 互換性テスト: すべてのデバイスと 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 とビジネス ロジック を Composable、ViewModel、ドメイン レイヤなどの別の場所に移動します。
  • ビジネス ロジックを含むクラスで直接 フレームワークの依存関係 を使用しないでください。 たとえば、ViewModel で Android Context を使用しないでください。
  • 依存関係を簡単に 置き換え られるようにします。たとえば、具体的な実装の代わりに インターフェースを使用します。DI フレームワークを使用しない場合でも、 依存性注入を使用します。

次のステップ

テストを行う理由と、2 つの主なテストの種類を理解したら、 テスト対象またはテスト戦略について学習してください。

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