1. 始める前に
以前の Codelab で、単体テストとインストルメンテーション テストの両方について作成して実行する方法を学習しました。この Codelab では、テストを作成する際のおすすめの方法と、テスト用の Gradle 依存関係を追加する方法について説明します。また、単体テストとインストルメンテーション テストの作成について、さらに演習を行います。
前提条件
- Android Studio で既存のプロジェクトを開いたことがある。
- Android Studio で単体テストとインストルメンテーション テストを作成したことがある。
- Android Studio でプロジェクトを操作した経験がある。
- Android Studio で
build.gradle
ファイルを扱った経験がある。
学習内容
- テスト作成の基本
- テスト用の Gradle 依存関係を追加する方法
- インストルメンテーション テストでリストをテストする方法
必要なもの
- Android Studio がインストールされているパソコン
- Affirmations アプリの解答コード
この Codelab のスターター コードをダウンロードする
この Codelab では、Affirmations アプリの解答コードにインストルメンテーション テストを追加します。
- プロジェクト用に提供されている GitHub リポジトリ ページに移動します。
- ブランチ名が Codelab で指定されたブランチ名と一致していることを確認します。たとえば、次のスクリーンショットでは、ブランチ名は main です。
- プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ポップアップが表示されます。
- ポップアップで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ちます。
- パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
- ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。
Android Studio でプロジェクトを開く
- Android Studio を起動します。
- [Welcome to Android Studio] ウィンドウで、[Open] をクリックします。
注: Android Studio がすでに開いている場合は、メニューから [File] > [Open] を選択します。
- ファイル ブラウザで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
- そのプロジェクト フォルダをダブルクリックします。
- Android Studio でプロジェクトが開かれるまで待ちます。
- 実行ボタン をクリックして、アプリをビルドし、実行します。期待どおりにビルドされることを確認します。
2. スターター アプリの概要
Affirmations アプリは 1 画面で構成されており、その画面にはアファメーションの言葉と組み合わせた画像のリストが表示されます。
3. おすすめの方法
意図的に、テストコードはアプリのビジネス ロジックとは異なるようにします。これは、テストコードはロジックをテストするだけのものであり、ロジックを含むべきでないからです。そのため、テストに if
や when
などの条件ステートメント、for
や while
などの制御フロー ステートメントを含めないようにします。また、値の操作や計算も含めません。
テストによってはこれらが必要となる場合もありますが、基本的には避けるようにします。このようなロジックは、アプリの一部としてテストされるべきものであり、テストに含めてしまうと、アプリコードがエラーになるのと同様にテストがエラーになる可能性があるからです。
単体テストでは、アプリからテストに必要な部分のコードのみを呼び出し、そのコードを呼び出した結果の値または状態をテストするようにします。UI テストでは、ユーザー インターフェースの想定される状態のみをテストするようにします。このコンセプトが身に付くまでには時間がかかるかもしれませんが、問題ありません。今後の Codelab で、このコンセプトの把握に役立つトピックをいくつか取り上げます。それまでの間、テストを作成する際には、特に注意してこのアプローチを適用するようにしてください。
4. テスト ディレクトリを作成する
以前の Codelab で、インストルメンテーション テスト用の androidTest
ディレクトリの作成方法を学習しました。このプロジェクトでは、そのときの手順を androidTest
ディレクトリと test
ディレクトリの両方に対して行います。手順は両方に共通で、唯一異なるのは、test
ディレクトリの場合は [New Directory] プルダウンから [androidTest/java] ではなく [test/java] を選択する必要があるという点です。作成したディレクトリごとに、com.example.affirmations という新しいパッケージを作成します。
5. インストルメンテーション テスト クラスを作成する
[androidTest] -> [com.example.affirmations] に AffirmationsListTests.kt
という新しいクラスを作成します。
Dice Roller アプリと同様に、Affirmations アプリにも 1 つのアクティビティしかありません。アクティビティの UI をテストするには、そのアクティビティを起動するよう指定する必要があります。その方法を思い出してみてください。
- 新しく作成したクラスにテストランナーを追加します。
@RunWith(AndroidJUnit4::class)
- メイン アクティビティに対するアクティビティ シナリオ ルールを作成します。
@get:Rule
val activity = ActivityScenarioRule(MainActivity::class.java)
- Affirmations アプリは、画像とそれに対応する肯定的アファメーションのリストを表示します。これらのアイテムを UI で操作(選択、スワイプなど)することはできません。したがって、このアプリの場合、インストルメンテーション テストでテストするのは静的データのみです。
scroll_to_item()
というテストメソッドを作成します。忘れずに@Test
アノテーションを付けてください。
このテストでは、リスト内の特定のアイテムまでスクロールする必要があります。そのアプローチは、このプロジェクトが参照していないメソッドを必要とするため、まだ使用できません。テストを進めるためには、テスト用の依存関係を追加する必要があります。
6. インストルメンテーション テストの依存関係の追加
アプリコードで使用する Gradle 依存関係の追加については、すでにある程度経験があるはずです。Gradle では、単体テストやインストルメンテーション テストに専用の依存関係を追加することもできます。[app] -> [build.gradle] にあるアプリレベルの build.gradle
ファイルを開きます。dependencies セクションの依存関係の実装には、implementation
、testImplementation
、androidTestImplementation
の 3 種類があります。
implementation
はアプリ自体で使用する依存関係用、testImplementation
は単体テストで使用する依存関係用、androidTestImplementation
はインストルメンテーション テストで使用する依存関係用です。
- インストルメンテーション テストで
RecyclerView
を操作できるように、依存関係を追加します。次のライブラリをandroidTestImplementation
として追加します。
androidx.test.espresso:espresso-contrib:3.4.0
依存関係は次のようになります。
dependencies {
...
androidTestImplementation
'androidx.test.espresso:espresso-contrib:3.4.0'
}
- 次に、プロジェクトを同期します。
7. RecyclerView をテストする
- プロジェクトを同期したら、
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
)を渡します。
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. ローカルテストの依存関係の追加
- この Codelab では、これまでに 3 種類の依存関係の実装について学習し、インストルメンテーション テストの依存関係を追加しました。ここでは、ローカルテストの依存関係を追加します。[app] -> [build.gradle] に移動し、単体テストの依存関係として次を追加します。
org.mockito:mockito-core:3.12.4
依存関係は次のようになります。
dependencies {
...
testImplementation 'org.mockito:mockito-core:3.12.4'
}
- 次に、プロジェクトを同期します。
10. アダプターをテストする
このアプリは、テストするロジックがあまりないため、単体テストに適していません。それでも、今後のテストの準備として、さまざまなコンポーネントをテストする経験にはなります。
- 単体テストクラスに次の行を追加します。
private val context = mock(Context::class.java)
mock()
メソッドは、折よくこのプロジェクトで実装したライブラリに含まれます。モックは単体テストに欠かせない要素ですが、この Codelab の対象外です。モックについては、別の Codelab で詳しく説明します。Android において Context
はアプリの現在の状態を表すコンテキストですが、単体テストは実際のデバイスではなく JVM で実行されるため、Context
は存在しないことにご注意ください。モックメソッドを使用すると、Context
の「モックされた」インスタンスを作成できます。このインスタンスは、実際の機能は含みませんが、コンテキストを必要とするメソッドのテストに使用できます。
adapter_size()
という関数を作成し、テストのアノテーションを付けます。このテストでは、アダプターのサイズが、アダプターに渡されたリストのサイズであることを確認します。これを行うには、ItemAdapter
のインスタンスを作成し、Datasource
クラスのloadAffirmations()
メソッドから返されたリストを渡します。または、新しいリストを作成してテストします。単体テストでは、テスト専用の独自のデータを作成するのがおすすめの方法です。そのため、このテスト用のカスタムリストを作成します。
val data = listOf(
Affirmation(R.string.affirmation1, R.drawable.image1),
Affirmation(R.string.affirmation2, R.drawable.image2)
)
- 次に、
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
- これが、テスト対象のメソッドです。このメソッドから返された値が、ステップ 2 で作成したリストのサイズと一致することを確認します。
assertEquals()
メソッドを使用して、リストサイズとアダプター サイズの値を比較します。
assertEquals("ItemAdapter is not the correct size", data.size, adapter.itemCount)
assertEquals()
メソッドはすでに見慣れているかもしれませんが、上の行については全体をよく確認するようおすすめします。最初のパラメータは、テストが失敗した場合にテスト結果に表示される文字列です。2 つ目のパラメータは想定値です。3 つ目のパラメータは実際の値です。今回のテストクラスは次のようになります。
- テストを実行します。
11. 解答コード
12. 完了
この Codelab では次のことを学びました。
- テスト用の依存関係を追加する方法
- インストルメンテーション テストで
RecyclerView
を操作する方法 - テストのための基本的なおすすめの方法