テストの基本

ユーザーは、ボタンの押下からデバイスへの情報のダウンロードまで、さまざまなレベルでアプリを操作します。したがって、アプリの反復型開発を行う過程で、さまざまなユースケースとインタラクションをテストする必要があります。

テスト用にコードを編成する

アプリの機能が拡張されるにつれて、サーバーからデータを取得する、デバイスのセンサーとやり取りする、ローカル ストレージにアクセスする、複雑なユーザー インターフェースをレンダリングするなどの処理が必要になることがあります。アプリを多様な用途で使用するには、包括的なテスト戦略が必要です。

コードの作成とテストを反復的に行う

機能の反復型開発を行う場合は、新しいテストを作成するか、または既存の単体テスト(ユニットテスト)にケースとアサーションを追加することから始めます。機能がまだ実装されていないため、最初はテストがエラーになります。

新しい機能に影響するユニットを考慮することは重要です。こうしたユニットは機能の設計段階でわかってきます。ユニットごとに、対応する単体テストを作成します。単体テストでは、標準的な操作、無効な入力、リソースが利用できない場合の処理など、ユニットについて考えられるすべてのインタラクションをできる限りテストする必要があります。可能な限り Jetpack ライブラリを活用してください。十分にテストが行われたライブラリですから、うまく利用することでアプリの個別の動作の検証に専念できます。

テスト開発サイクルは、エラーを発生させる単体テストの作成、テストに合格するコードの作成、そしてリファクタリングで構成されます。機能の開発サイクル全体が、より大きな UI ベースサイクル内の 1 つのステップとに位置づけられます。
図 1. テスト主導の反復型開発を行う場合の 2 つのサイクル

図 1 に示すワークフローの全体図には、入れ子構造になった反復サイクルが示されています。時間がかかり長期に渡る UI 中心のテストサイクルでは、コードユニットの統合をテストします。比較的早く期間の短い開発サイクルでは、ユニットそのもののテストを行います。このような一連のサイクルは、アプリがすべてのユースケースでテストに合格するまで続きます。

アプリを一連のモジュールとして見る

コードのテストを容易にするには、モジュールの観点に立ってコードを開発します。つまり、各モジュールは、ユーザーがアプリ内で実行する特定のタスクを表すものと考えます。このような見方は、アプリを UI、ビジネス ロジック、データに対応するレイヤで構成されたものとして捉えることの多い、スタックベースの視点とは対照的です。

たとえばタスクリスト アプリの場合、タスクの作成、完了したタスクに関する統計の表示、特定のタスクに関連付けられる写真の撮影を行うモジュールなどが考えられます。また、このようなモジュール型のアーキテクチャを採用することで、関連のないクラスを分離した状態に保つことができ、開発チーム内でのオーナー権限の割り当てが行いやすい構造になります。

各モジュールの間に明確に定義された境界を設定し、アプリの規模と複雑さの進展に合わせて、新しいモジュールを作成していくことが重要です。それぞれのモジュールが担当する内容を 1 つに絞り、モジュール間の通信を可能にする API の一貫性を確保する必要があります。モジュール間のインタラクションを簡単かつ迅速にテストするため、モジュールの仮の実装を作成することを検討してください。テストでは、実際に実装するモジュールから、他のモジュールの仮の実装を呼び出すことができます。

なお、新しいモジュールを作成する際には、完全な機能の短期間での実現にこだわる必要はありません。モジュールに、アプリのスタックのレイヤーが 1 つないし複数なくても特に問題はありません。

アプリでモジュールを定義する方法と、モジュールを作成および公開するためのプラットフォーム サポートの詳細については、Android アプリバンドルをご覧ください。

テスト環境を構成する

アプリでテストを作成するための環境と依存関係をセットアップする際は、このセクションで説明するおすすめの方法に従ってください。

実行環境に基づいてテスト ディレクトリを編成する

Android Studio の一般的なプロジェクトには、テストを配置するディレクトリが 2 つ含まれています。次のようにテストを配置してください。

  • androidTest ディレクトリには、実際のデバイスまたは仮想デバイスで実行するテストを配置します。この種のテストには、統合テストとエンドツーエンド テストに加えて、JVM だけではアプリの機能を検証できない場合のテストがあります。
  • test ディレクトリには、単体テストなど、ローカルマシンで実行するテストを配置します。

さまざまなタイプのデバイスでテストを実行する場合のトレードオフを検討する

デバイスでテストを実行する場合、次のタイプのデバイスを選択できます。

  • 実際のデバイス
  • 仮想デバイス(Android Studio のエミュレータなど)
  • シミュレートされたデバイス(Robolectric など)

再現性が最も高いのは実際のデバイスですが、テストの実行に時間がかかります。一方、シミュレートされたデバイスでは、テスト速度は向上しますが、再現性が低下します。ただし、バイナリ リソース レベルでのプラットフォームの改善や、現実的なループの採用により、シミュレートされたデバイスでもより実際に近い結果が再現できます。

仮想デバイスでは、再現性とテスト速度のバランスを適度に保つことができます。仮想デバイスでテストする場合は、スナップショットを使用すると、テスト間でのセットアップ時間を最小限に抑えられます。

テストダブルを使用するかどうかを検討する

テストを作成する場合、実際にオブジェクトを作成する方法と、テストダブル(仮のオブジェクトやモック オブジェクト)を作成する方法があります。一般的には、実際のオブジェクトをテストで使用する方がテストダブルを使用するよりも適切です。テスト対象のオブジェクトが次の条件のいずれかに当てはまる場合は特にそうです。

  • オブジェクトがデータ オブジェクトである。
  • オブジェクトが実際の依存関係のオブジェクトと通信しないと機能しない。典型的な例としては、イベント コールバック ハンドラが挙げられます。
  • オブジェクトの依存関係との接続を複製することが困難。典型的な例としては、SQL のデータベース ハンドラが挙げられます。この場合、データベース結果に仮のデータを使用するよりも、インメモリ データベースを使用する方が、テストの確実性が増します。

特に、自分が管理していない型のインスタンスのモックを作成する場合は、テストが不確実なものになりがちです。他人による型の実装を正確に把握しないと、テストがうまくいかないからです。このようなモックの使用は、最後の手段としてください。自身が管理するオブジェクトのモックを作成する場合は問題ありませんが、クラス内のすべての機能をスタブ化したモックよりも、@Spy アノテーションを付けたモックの方が再現性が高いことに留意してください。

ただし、テストで実際のオブジェクトに次のタイプのオペレーションを実行する場合は、仮のオブジェクトまたはモック オブジェクトを作成することをおすすめします。

  • 大きなファイルの処理など、時間のかかるオペレーション。
  • 任意のオープン状態のポートへの接続など、隔離されていないアクション。
  • 作成するのが難しい構成。

ヒント: ライブラリ作成者に、公式にサポートされており、信頼できるテスト インフラストラクチャ(仮のオブジェクトなど)が提供されているか確認してください。

テストを作成する

テスト環境を構成し終えたら、アプリの機能を評価するテストを作成します。このセクションでは、小規模テスト、中規模テスト、大規模テストの作成方法について説明します。

テスト ピラミッドの階層

3 つの階層で構成されたピラミッド
図 2. テスト ピラミッド。アプリのテストスイートに含める必要がある 3 つのテストカテゴリを示しています

図 2 に示すテスト ピラミッドは、3 つのテストカテゴリ(小規模、中規模、大規模)について、アプリで扱うべき程度を示しています。

  • 小規模テストは、1 つのクラスごとにアプリの動作を検証する単体テストです。
  • 中規模テストは、モジュール内のスタックレベルでのインタラクション、または関連するモジュール間のインタラクションを検証する統合テストです。
  • 大規模テストは、アプリの複数のモジュールにまたがってユーザーが経験する過程を検証するエンドツーエンド テストです。

小規模テストから大規模テストへとピラミッドの段階が上がるにつれて、各テストの再現性は向上しますが、実行時間と保守およびデバッグの労力が増大します。したがって、統合テストより単体テストを増やし、エンドツーエンド テストより統合テストを増やす必要があります。各カテゴリのテストの割合はアプリのユースケースに応じて異なりますが、一般的には、小規模テスト 70%、中規模テスト 20%、大規模テスト 10% の割合でテストを作成することをおすすめします。

Android のテスト ピラミッドの詳細については、Google I/O 2017 の Android でのテスト主導型開発セッション動画(1 分 51 秒以降)をご覧ください。

小規模テストを作成する

小規模テストは、対象を絞り込んだ単体テストとし、アプリ内の各クラスの機能とコントラクトを徹底的に検証する必要があります。

クラス内のメソッドの追加または変更を行った場合、該当箇所に対する単体テストを作成して実行します。テストが Android フレームワークに依存する場合は、androidx.test API などのデバイスに依存しない統合 API を使用します。このように一貫した対応をとることで、物理デバイスやエミュレータを使用することなく、ローカルでテストを実行できます。

テストがリソースに依存する場合は、アプリの build.gradle ファイルincludeAndroidResources オプションを有効にします。これにより、単体テストでリソースのコンパイル済みバージョンを利用できるようになるため、迅速かつ正確にテストを実行できます。

app/build.gradle

    android {
        // ...

        testOptions {
            unitTests {
                includeAndroidResources = true
            }
        }
    }
    

ローカル単体テスト

単体テストをデバイスまたはエミュレータで実行するには、可能な限り AndroidX Test API を使用します。テストの際に、常に JVM に対応した開発マシンを利用していれば、Robolectric が使用できます。

Robolectric は、Android 4.1(API レベル 16)以上のランタイムをシミュレートし、コミュニティで管理されるシャドウという仮のオブジェクトを提供しています。この機能を使用すると、エミュレータまたはモック オブジェクトを使用せずに、フレームワークに依存するコードをテストできます。Robolectric は、Android プラットフォームの次の領域をサポートしています。

  • コンポーネント ライフサイクル
  • イベントループ
  • すべてのリソース

インストゥルメント化単体テスト

物理デバイスまたはエミュレータで、インストゥルメント化単体テストを実行できます。ただし、このテストではローカル単体テストより実行時間が大幅に遅くなるため、実際のデバイス ハードウェアでのアプリの動作評価が必要な場合にのみ、利用することをおすすめします。

インストゥルメント化単体テストを実行する場合、AndroidX Test は次のスレッドを使用します。

  • メインスレッド。「UI スレッド」または「アクティビティ スレッド」とも呼ばれます。このスレッドでは、UI インタラクションおよびアクティビティ ライフサイクル イベントが発生します。
  • インストゥルメント化スレッド。ほとんどのテストが実行されます。テストスイートが開始されると、AndroidJUnitTest クラスがこのスレッドを開始します。

メインスレッドでテストを実行する必要がある場合は、@UiThreadTest アノテーションを付けます。

中規模テストを作成する

小規模テストを実行してアプリの各ユニットをテストするだけでなく、モジュール レベルでアプリの動作を検証する必要もあります。そのためには、中規模テストを作成します。これは、ユニットのグループのコラボレーションとインタラクションを検証する統合テストです。

アプリの構造と、以下に示す中規模テストの例(スコープが狭いものから順に記載しています)を使用して、アプリ内のユニット グループを表す最適な方法を定義します。

  1. ビューとビューモデル間のインタラクション(Fragment オブジェクトのテスト、レイアウト XML の検証、ViewModel オブジェクトのデータ バインディング ロジックの評価など)。
  2. アプリのリポジトリ レイヤのテスト。さまざまなデータソースとデータアクセス オブジェクト(DAO)が期待どおりに連携することを確認します。
  3. アプリの垂直スライス。特定の画面でのインタラクションをテストします。このタイプのテストでは、アプリのスタックのレイヤ全体のインタラクションを検証します。
  4. アプリの特定の領域を評価するマルチフラグメント テスト。このリストに記載している他のタイプの中規模テストと異なり、このタイプのテストでは、通常、実際のデバイスが必要です。これは、テスト対象のインタラクションに複数の UI 要素が関係するためです。

上記のテストを実行するには、次の手順に従います。

  1. Espresso Intents ライブラリのメソッドを使用します。テストに渡す情報を簡素化するには、仮のデータやスタブを使用します。
  2. IntentSubject と Truth ベースのアサーションを組み合わせて使用し、キャプチャされたインテントを検証します。

インストゥルメント化中規模テストの実行に Espresso を使用する

Espresso は、デバイスまたは Robolectric で、次のような UI インタラクションを実行する際にタスクの同期を維持するのに役立ちます。

  • View オブジェクトでアクションを実行する。
  • ユーザー補助を必要とするユーザーがアプリをどの程度使用できるか評価する。
  • RecyclerView および AdapterView オブジェクト内のアイテムを検出してアクティブ化する。
  • 発信インテントの状態を検証する。
  • WebView オブジェクト内の DOM の構造を検証する。

これらのインタラクションの詳細と、アプリのテストでそれらを使用する方法については、Espresso ガイドをご覧ください。

大規模テストを作成する

アプリ内のクラスとモジュールを個別にテストすることは重要です。一方、ユーザーが複数のモジュールと機能を利用する際のエンドツーエンドのワークフローを検証することも同じくらい重要です。このタイプのテストでは、コード内でのボトルネックの発生が避けられませんが、可能な限り実際の完成版に近いアプリを検証することにより、その影響を最小限に抑えることができます。

アプリの機能を全体的に評価する場合、アプリがそれほど大きくなければ、必要となる大規模テストスイートが 1 つで済むこともあります。そうでない場合は、チームのオーナー権限、機能の種類、ユーザー目標で大規模テストスイートを分割する必要があります。

一般的に、物理デバイスよりも、エミュレートされたデバイスまたはクラウドベースのサービス(Firebase Test Lab など)でアプリをテストする方が適切です。画面サイズとハードウェア構成のさまざまな組み合わせを簡単かつ迅速にテストできるからです。

Espresso による同期のサポート

Espresso は、中規模のインストゥルメンテーション テストのサポートに加えて、大規模テストで次のようなタスクを実行する際の同期のサポートも提供します。

  • アプリの各プロセスの区分を越えるワークフローを実行する。 Android 8.0(API レベル 26)以上でのみ利用可能です。
  • アプリ内で長時間実行されるバックグラウンド オペレーションをトラッキングする。
  • デバイス外のテストを実行する。

これらのインタラクションの詳細と、アプリのテストでそれらを使用する方法については、Espresso ガイドをご覧ください。

AndroidX Test でその他のテストタスクを実行する

このセクションでは、AndroidX Test の要素を使用してアプリのテストをさらに改善する方法について説明します。

Truth を使用して読みやすいアサーションを作成する

Guava チームは、Truth という可読性の高いアサーション ライブラリを提供しています。テストの検証ステップ(then ステップ)を構築する際、このライブラリを JUnit ベースまたは Hamcrest ベースのアサーションの代わりに使用できます。

通常は、Truth を使用して、オブジェクトに特定のプロパティがあることを、次のようなテスト対象の条件を含むフレーズで表現します。

  • assertThat(object).hasFlags(FLAGS)
  • assertThat(object).doesNotHaveFlags(FLAGS)
  • assertThat(intent).hasData(URI)
  • assertThat(extras).string(string_key).equals(EXPECTED)

AndroidX Test は、次に示す Android の追加サブジェクトをサポートしており、それによって Truth ベースのアサーションの構築がさらに容易になります。

AndroidX Test API は、モバイルアプリのテストに関連する一般的なタスクの実行に役立ちます。これについては、次のセクションで説明します。

UI テストを作成する

Espresso を使用すると、プログラムを介して、スレッドセーフな方法でアプリ内の UI 要素を検出および操作できます。詳細については、Espresso ガイドをご覧ください。

UI テストを実行する

AndroidJUnitRunner クラスは、Android デバイスで JUnit 3 または JUnit 4 形式のテストクラスを実行するためのインストルメンテーション ベースの JUnit テストランナーを定義します。テストランナーを使用すると、テスト パッケージとテスト対象アプリをデバイスまたはエミュレータに読み込み、テストを実行し、結果をレポートする処理が容易になります。

テストの信頼性をさらに高めるには、Android Test Orchestrator を使用して、独自の Instrumentation サンドボックスで個別の UI テストを実行します。このアーキテクチャでは、テスト間で共有される状態が減少し、アプリのクラッシュをテストごとに分離できます。アプリをテストする際に Android Test Orchestrator で得られる利点の詳細については、Android Test Orchestrator ガイドをご覧ください。

視覚要素を操作する

UI Automator API を使用すると、フォーカスを持つアクティビティまたはフラグメントとは無関係に、デバイス上の視覚要素を操作できます。

注意: UI Automator でのアプリのテストは、重要なユースケースを扱うために、アプリがシステム UI または別のアプリとやり取りする必要がある場合だけに限定することをおすすめします。UI Automator は特定のシステム UI とやり取りするため、プラットフォームのバージョン アップグレードや Google Play 開発者サービスの新規リリースのたびに、UI Automator テストを再実行して修正する必要が生じます。

UI Automator を使用する代わりに、隔離テストを追加するか、大規模テストを小規模テストと中規模テストのスイートに分割することをおすすめします。特に、アプリ間での通信処理などは一度に 1 つの処理に限定してテストします。たとえば、他のアプリに情報を送信する処理や、インテント結果に応答する処理などです。Espresso-Intents ツールは、このような小規模テストの作成に役立ちます。

ユーザー補助チェックを追加して一般的なユーザビリティを検証する

アプリのインターフェースは、すべてのユーザーが簡単にデバイスを操作し、アプリのタスクを完了できるようなものにする必要があります。また、ユーザー補助を必要とするユーザーについても考慮します。

Android のテスト ライブラリには、アプリのユーザー補助機能を検証するための機能が、組み込みでいくつか用意されています。これについては次のセクションで説明します。さまざまなタイプのユーザーを対象に、アプリの使いやすさを検証する方法の詳細については、アプリのユーザー補助機能をテストするためのガイドをご覧ください。

Robolectric

テストスイートの先頭に @AccessibilityChecks アノテーションを付けることで、ユーザー補助チェックを有効にします。次のコード スニペットをご覧ください。

Kotlin

    import org.robolectric.annotation.AccessibilityChecks

    @AccessibilityChecks
    class MyTestSuite {
        // Your tests here.
    }
    

Java

    import org.robolectric.annotation.AccessibilityChecks;

    @AccessibilityChecks
    public class MyTestSuite {
        // Your tests here.
    }
    

Espresso

テストスイートの setUp() メソッドで AccessibilityChecks.enable() を呼び出すことで、ユーザー補助チェックを有効にします。下記のコード スニペットをご覧ください。

ユーザー補助チェックの結果の解釈方法について詳しくは、Espresso のユーザー補助チェックのガイドをご覧ください。

Kotlin

    import androidx.test.espresso.accessibility.AccessibilityChecks

    @Before
    fun setUp() {
        AccessibilityChecks.enable()
    }
    

Java

    import androidx.test.espresso.accessibility.AccessibilityChecks;

    @Before
    public void setUp() {
        AccessibilityChecks.enable();
    }
    

アクティビティとフラグメントのライフサイクルを機能させる

システムレベルの中断や構成の変更があった場合に、アプリのアクティビティとフラグメントにどのようが反応があるかをテストするには、ActivityScenario クラスと FragmentScenario クラスを使用します。詳細については、アクティビティをテストする方法とフラグメントをテストする方法のガイドをご覧ください。

サービスのライフサイクルを管理する

AndroidX Test には、主要なサービスのライフサイクルを管理するためのコードが用意されています。そのためのルールを定義する方法については、JUnit4 ルールのガイドをご覧ください。

SDK のバージョンごとに異なる動作について、すべてのバリエーションを評価する

アプリの動作がデバイスの SDK バージョンに依存する場合は、@SdkSuppress アノテーションを使用し、アプリのロジックの分岐方法に応じて、minSdkVersion または maxSdkVersion の値を渡します。

Kotlin

    @Test
    @SdkSuppress(maxSdkVersion = 27)
    fun testButtonClickOnOreoAndLower() {
        // ...
    }

    @Test
    @SdkSuppress(minSdkVersion = 28)
    fun testButtonClickOnPieAndHigher() {
        // ...
    }
    

Java

    @Test
    @SdkSuppress(maxSdkVersion = 27)
    public void testButtonClickOnOreoAndLower() {
        // ...
    }

    @Test
    @SdkSuppress(minSdkVersion = 28)
    public void testButtonClickOnPieAndHigher() {
        // ...
    }
    

参考情報

Android でのテストの詳細については、次のリソースをご覧ください。

サンプル

コードラボ