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 をご覧ください。