リストとアダプターをテストする

1. 始める前に

以前の Codelab で、単体テストとインストルメンテーション テストの両方について作成して実行する方法を学習しました。この Codelab では、テストを作成する際のおすすめの方法と、テスト用の Gradle 依存関係を追加する方法について説明します。また、単体テストとインストルメンテーション テストの作成について、さらに演習を行います。

前提条件

  • Android Studio で既存のプロジェクトを開いたことがある。
  • Android Studio で単体テストとインストルメンテーション テストを作成したことがある。
  • Android Studio でプロジェクトを操作した経験がある。
  • Android Studio で build.gradle ファイルを扱った経験がある。

学習内容

  • テスト作成の基本
  • テスト用の Gradle 依存関係を追加する方法
  • インストルメンテーション テストでリストをテストする方法

必要なもの

  • Android Studio がインストールされているパソコン
  • Affirmations アプリの解答コード

この Codelab のスターター コードをダウンロードする

この Codelab では、Affirmations アプリの解答コードにインストルメンテーション テストを追加します。

  1. プロジェクト用に提供されている GitHub リポジトリ ページに移動します。
  2. ブランチ名が Codelab で指定されたブランチ名と一致していることを確認します。たとえば、次のスクリーンショットでは、ブランチ名は main です。

1e4c0d2c081a8fd2.png

  1. プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ポップアップが表示されます。

1debcf330fd04c7b.png

  1. ポップアップで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ちます。
  2. パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
  3. ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。

Android Studio でプロジェクトを開く

  1. Android Studio を起動します。
  2. [Welcome to Android Studio] ウィンドウで、[Open] をクリックします。

d8e9dbdeafe9038a.png

注: Android Studio がすでに開いている場合は、メニューから [File] > [Open] を選択します。

8d1fda7396afe8e5.png

  1. ファイル ブラウザで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
  2. そのプロジェクト フォルダをダブルクリックします。
  3. Android Studio でプロジェクトが開かれるまで待ちます。
  4. 実行ボタン 8de56cba7583251f.png をクリックして、アプリをビルドし、実行します。期待どおりにビルドされることを確認します。

2. スターター アプリの概要

Affirmations アプリは 1 画面で構成されており、その画面にはアファメーションの言葉と組み合わせた画像のリストが表示されます。

3. おすすめの方法

意図的に、テストコードはアプリのビジネス ロジックとは異なるようにします。これは、テストコードはロジックをテストするだけのものであり、ロジックを含むべきでないからです。そのため、テストに ifwhen などの条件ステートメント、forwhile などの制御フロー ステートメントを含めないようにします。また、値の操作や計算も含めません。

テストによってはこれらが必要となる場合もありますが、基本的には避けるようにします。このようなロジックは、アプリの一部としてテストされるべきものであり、テストに含めてしまうと、アプリコードがエラーになるのと同様にテストがエラーになる可能性があるからです。

単体テストでは、アプリからテストに必要な部分のコードのみを呼び出し、そのコードを呼び出した結果の値または状態をテストするようにします。UI テストでは、ユーザー インターフェースの想定される状態のみをテストするようにします。このコンセプトが身に付くまでには時間がかかるかもしれませんが、問題ありません。今後の Codelab で、このコンセプトの把握に役立つトピックをいくつか取り上げます。それまでの間、テストを作成する際には、特に注意してこのアプローチを適用するようにしてください。

4. テスト ディレクトリを作成する

以前の Codelab で、インストルメンテーション テスト用の androidTest ディレクトリの作成方法を学習しました。このプロジェクトでは、そのときの手順を androidTest ディレクトリと test ディレクトリの両方に対して行います。手順は両方に共通で、唯一異なるのは、test ディレクトリの場合は [New Directory] プルダウンから [androidTest/java] ではなく [test/java] を選択する必要があるという点です。作成したディレクトリごとに、com.example.affirmations という新しいパッケージを作成します。

d762ecd8950e97b2.png

5. インストルメンテーション テスト クラスを作成する

[androidTest] -> [com.example.affirmations]AffirmationsListTests.kt という新しいクラスを作成します。

Dice Roller アプリと同様に、Affirmations アプリにも 1 つのアクティビティしかありません。アクティビティの UI をテストするには、そのアクティビティを起動するよう指定する必要があります。その方法を思い出してみてください。

  1. 新しく作成したクラスにテストランナーを追加します。
@RunWith(AndroidJUnit4::class)
  1. メイン アクティビティに対するアクティビティ シナリオ ルールを作成します。
@get:Rule
val activity = ActivityScenarioRule(MainActivity::class.java)
  1. Affirmations アプリは、画像とそれに対応する肯定的アファメーションのリストを表示します。これらのアイテムを UI で操作(選択、スワイプなど)することはできません。したがって、このアプリの場合、インストルメンテーション テストでテストするのは静的データのみです。scroll_to_item() というテストメソッドを作成します。忘れずに @Test アノテーションを付けてください。

このテストでは、リスト内の特定のアイテムまでスクロールする必要があります。そのアプローチは、このプロジェクトが参照していないメソッドを必要とするため、まだ使用できません。テストを進めるためには、テスト用の依存関係を追加する必要があります。

6. インストルメンテーション テストの依存関係の追加

アプリコードで使用する Gradle 依存関係の追加については、すでにある程度経験があるはずです。Gradle では、単体テストやインストルメンテーション テストに専用の依存関係を追加することもできます。[app] -> [build.gradle] にあるアプリレベルの build.gradle ファイルを開きます。dependencies セクションの依存関係の実装には、implementationtestImplementationandroidTestImplementation の 3 種類があります。

implementation はアプリ自体で使用する依存関係用、testImplementation は単体テストで使用する依存関係用、androidTestImplementation はインストルメンテーション テストで使用する依存関係用です。

  1. インストルメンテーション テストで RecyclerView を操作できるように、依存関係を追加します。次のライブラリを androidTestImplementation として追加します。
androidx.test.espresso:espresso-contrib:3.4.0

依存関係は次のようになります。

dependencies {
    ...
    androidTestImplementation
'androidx.test.espresso:espresso-contrib:3.4.0'
}
  1. 次に、プロジェクトを同期します。

7. RecyclerView をテストする

  1. プロジェクトを同期したら、AffirmationsListTests.kt ファイルに戻ります。onView() で操作対象の ViewInteraction を指定します。onView() メソッドには ViewMatcher を渡す必要があります。ここには withId() を指定して、アファメーション用の RecyclerView の ID を渡します。次に、ViewInteraction に対して perform() を呼び出します。ここで、新たに追加した依存関係が機能します。それにより RecyclerViewActions.scrollToPosition<RecyclerView.Viewholder>(9) ViewAction を渡せるようになりました。
onView(withId(R.id.recycler_view)).perform(
   RecyclerViewActions
       .scrollToPosition<RecyclerView.ViewHolder>(9)
)

この行の構文を理解することは重要ではありませんが、確認しておく価値はあります。RecyclerViewActions は、名前が示すとおり、テストで RecyclerView を操作できるようにするクラスです。scrollToPosition() は、指定された位置にスクロールする RecyclerViewActions クラスの静的メソッドです。このメソッドは、いわゆるジェネリックを返します。ジェネリックはこの Codelab の範囲外ですが、この場合、RecyclerView 内のアイテムが何であれ、それを返す scrollToPosition() メソッドと考えることができます。

このアプリでは、RecyclerView 内のアイテムが ViewHolder であるため、メソッド呼び出しの後に山かっこで囲んで RecyclerView.ViewHolder を指定します。最後に、リストの最後の位置(9)を渡します。

  1. RecyclerView の目的の位置へのスクロールが可能になったので、UI に想定どおりの情報が表示されていることを確認するアサーションを作成しましょう。最後のアイテムまでスクロールしたら、最後のアファメーションに関連付けられたテキストが表示されることを確認します。まずは ViewInteraction ですが、今回は新しい ViewMatcher(ここでは withText())を渡します。このメソッドに、最後のアファメーションのテキストを含む文字列リソースを渡します。withText() メソッドは、UI コンポーネントの特定を、そこに表示されているテキストに基づいて行います。このコンポーネントに対してしなければならないのは、目的のテキストが表示されているかをチェックすることのみです。それには、ViewInteraction に対して check() を呼び出します。check() には、matches() メソッドを使用できる ViewAssertion が必要です。最後に、メソッド isDisplayed() を渡して、UI コンポーネントが表示されているというアサーションを作成します。
onView(withText(R.string.affirmation10))
    .check(matches(isDisplayed()))

スクロールする位置のハード コーディングに関する注に話を戻すと、RecyclerViewActions を使用するとこの問題を解決できます。リストの長さが不明な場合は、scrollTo アクションを使用します。特定のアイテムを見つけるには、scrollTo 関数に Matcher<View!>! が必要です。これにはさまざまな値を利用できますが、このテストの目的を満たすには withText を使用します。これを先ほど作成したテストに適用すると、コードは次のようになります。

onView(withId(R.id.recycler_view)).perform(
   RecyclerViewActions
       .scrollTo<RecyclerView.ViewHolder>(
           withText(R.string.affirmation10)
       )
)

onView(withText(R.string.affirmation10))
    .check(matches(isDisplayed())
)

これで、テストを実行する準備が整いました。デバイスまたはエミュレータでリストが一番下までスクロールされ、テストは成功するはずです。テスト結果が正しいことを確認するには、文字列 ID を R.string.affirmation1 に置き換えます。今度は、スクロール後にこの文字列リソースが表示されず、テストは失敗するはずです。

RecyclerViewActions クラスにはさまざまなメソッドがありますので、使用できるメソッドを確認することをおすすめします。

8. ローカルテスト クラスを作成する

[test] -> [com.example.affirmations] に、AffirmationsAdapterTests.kt という名前の新しいクラスを作成します。

9. ローカルテストの依存関係の追加

  1. この Codelab では、これまでに 3 種類の依存関係の実装について学習し、インストルメンテーション テストの依存関係を追加しました。ここでは、ローカルテストの依存関係を追加します。[app] -> [build.gradle] に移動し、単体テストの依存関係として次を追加します。
org.mockito:mockito-core:3.12.4

依存関係は次のようになります。

dependencies {
    ...
    testImplementation 'org.mockito:mockito-core:3.12.4'
}
  1. 次に、プロジェクトを同期します。

10. アダプターをテストする

このアプリは、テストするロジックがあまりないため、単体テストに適していません。それでも、今後のテストの準備として、さまざまなコンポーネントをテストする経験にはなります。

  1. 単体テストクラスに次の行を追加します。
private val context = mock(Context::class.java)

mock() メソッドは、折よくこのプロジェクトで実装したライブラリに含まれます。モックは単体テストに欠かせない要素ですが、この Codelab の対象外です。モックについては、別の Codelab で詳しく説明します。Android において Context はアプリの現在の状態を表すコンテキストですが、単体テストは実際のデバイスではなく JVM で実行されるため、Context は存在しないことにご注意ください。モックメソッドを使用すると、Context の「モックされた」インスタンスを作成できます。このインスタンスは、実際の機能は含みませんが、コンテキストを必要とするメソッドのテストに使用できます。

  1. adapter_size() という関数を作成し、テストのアノテーションを付けます。このテストでは、アダプターのサイズが、アダプターに渡されたリストのサイズであることを確認します。これを行うには、ItemAdapter のインスタンスを作成し、Datasource クラスの loadAffirmations() メソッドから返されたリストを渡します。または、新しいリストを作成してテストします。単体テストでは、テスト専用の独自のデータを作成するのがおすすめの方法です。そのため、このテスト用のカスタムリストを作成します。
val data = listOf(
   Affirmation(R.string.affirmation1, R.drawable.image1),
   Affirmation(R.string.affirmation2, R.drawable.image2)
)
  1. 次に、ItemAdapter のインスタンスを作成し、前の手順で作成した context 変数と data 変数を渡します。
val adapter = ItemAdapter(context, data)

リサイクラー ビュー アダプターには、アダプターのサイズを返す getItemCount() というメソッドがあります。このアプリの場合、そのメソッドは次のようになります。

/**
* Return the size of your dataset (invoked by the layout manager)
*/
override fun getItemCount() = dataset.size
  1. これが、テスト対象のメソッドです。このメソッドから返された値が、ステップ 2 で作成したリストのサイズと一致することを確認します。assertEquals() メソッドを使用して、リストサイズとアダプター サイズの値を比較します。
assertEquals("ItemAdapter is not the correct size", data.size, adapter.itemCount)

assertEquals() メソッドはすでに見慣れているかもしれませんが、上の行については全体をよく確認するようおすすめします。最初のパラメータは、テストが失敗した場合にテスト結果に表示される文字列です。2 つ目のパラメータは想定値です。3 つ目のパラメータは実際の値です。今回のテストクラスは次のようになります。

f81a27f5c1cf055e.png

  1. テストを実行します。

11. 解答コード

12. 完了

この Codelab では次のことを学びました。

  • テスト用の依存関係を追加する方法
  • インストルメンテーション テストで RecyclerView を操作する方法
  • テストのための基本的なおすすめの方法