Navigation コンポーネントをテストする

1. 始める前に

前の Codelab では、Navigation コンポーネントを使用したナビゲーションについて学習しました。この Codelab では、Navigation コンポーネントをテストする方法を学びます。これは、Navigation コンポーネントを使用せずにナビゲーションをテストすることとは別なので注意してください。

要件

  • Android Studio でテスト ディレクトリを作成している
  • Android Studio で単体テストとインストルメンテーション テストを作成している
  • Android プロジェクトに Gradle の依存関係を追加している

学習内容

  • インストルメンテーション テストを使用して Navigation コンポーネントをテストする方法
  • コードを繰り返さずにテストを設定する方法

必要なもの

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

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

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

  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. スターター アプリの概要

Words アプリはリストを表示するホーム画面で構成され、リストアイテムごとにアルファベットの 1 文字が割り当てられています。文字をクリックすると、その文字で始まる単語のリストが表示される画面に移動します。

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

必要に応じて、前の Codelab で行ったのと同様に、Words アプリのインストルメンテーション テスト ディレクトリを作成します。すでに作成している場合は、必要な依存関係を追加するステップに進んでください。

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

androidTest フォルダに NavigationTests.kt という新しいクラスを作成します。

b023023a2ccc3813.png

5. 必要な依存関係を追加する

ナビゲーション コンポーネントをテストするには、特定の Gradle 依存関係が必要です。また、非常に特殊な方法でフラグメントをテストするための依存関係も追加します。アプリ モジュールの build.gradle ファイルに移動し、次の依存関係を追加します。

androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.4.0'
androidTestImplementation 'androidx.navigation:navigation-testing:2.5.2'

debugImplementation 'androidx.fragment:fragment-testing:1.5.3'

次に、プロジェクトを同期します。

6. Navigation コンポーネント テストを作成する

Navigation コンポーネントのテストは、通常のナビゲーションのテストとは異なります。通常のナビゲーションをテストする場合、デバイスまたはエミュレータでナビゲーションをトリガーして実行します。Navigation コンポーネントをテストする場合は、デバイスまたはエミュレータで視覚的なナビゲーションを行うことはありません。デバイスまたはエミュレータの表示内容を変えずにナビゲーション コントローラを強制的に移動して、ナビゲーション コントローラが正しいデスティネーションに到達することをチェックします。

  1. navigate_to_words_nav_component() というテスト関数を作成します。
  2. テストで Navigation コンポーネントを操作するには、いくつかの設定が必要です。navigate_to_words_nav_component() メソッドで、ナビゲーション コントローラのテスト インスタンスを作成します。
val navController = TestNavHostController(
   ApplicationProvider.getApplicationContext()
)
  1. Navigation コンポーネントは、フラグメントを使用して UI を操作します。テスト対象のフラグメントを分離するために使用可能な、ActivityScenarioRule と同等のフラグメントが存在するため、フラグメント固有の依存関係は必須となります。これはフラグメントへの移動を処理するための追加のコードなしで起動できるため、到達するのに多くのナビゲーションを必要とするフラグメントをテストする際に非常に便利です。
val letterListScenario = launchFragmentInContainer<LetterListFragment>(themeResId =
R.style.Theme_Words)

ここでは、LetterListFragment を起動することを指定しています。アプリのテーマを渡して、使用するテーマを UI コンポーネントに知らせる必要があります。そうしないと、テストがクラッシュする可能性があります。

  1. 最後に、ナビゲーション コントローラが起動済みのフラグメントに使用するナビゲーション グラフを明示的に宣言する必要があります。
letterListScenario.onFragment { fragment ->

   navController.setGraph(R.navigation.nav_graph)

   Navigation.setViewNavController(fragment.requireView(), navController)
}
  1. 次に、ナビゲーションを促すイベントをトリガーします。
onView(withId(R.id.recycler_view))
   .perform(RecyclerViewActions
       .actionOnItemAtPosition<RecyclerView.ViewHolder>(2, click()))

launchFragmentInContainer() メソッドを使用する場合、コンテナは移動先の他のフラグメントやアクティビティを認識しないため、実際のナビゲーションを行うことはできません。コンテナは、コンテナ内で起動するよう指定したフラグメントのみを認識します。したがって、デバイスまたはエミュレータでこのテストを実行する場合、実際のナビゲーションは表示されません。これは直感的でないように思えますが、現在のデスティネーションについてより直接的なアサーションを行うことができます。特定の画面に表示されることがわかっている UI コンポーネントを探す代わりに、現在のナビゲーション コントローラのデスティネーションに、想定されるフラグメントの ID があるかどうかを確認できます。これは前述の方法よりもはるかに信頼性の高い方法です。

assertEquals(navController.currentDestination?.id, R.id.wordListFragment)

テストは次のようになります。78b4a72f75134ded.png

7. 解答コード

8. アノテーション付きのコードの繰り返しを避ける

Android では、インストルメンテーション テストと単体テストの両方に、コードを繰り返すことなくクラス内のすべてのテストに同じ構成を設定できる機能が用意されています。

たとえば、10 個のボタンを持つフラグメントがあるとします。各ボタンをクリックすると、一意のフラグメントが表示されます。

前述のテストのパターンに従った場合、10 回のテストごとに次のようなコードを繰り返す必要があります(このコードはあくまでサンプルであり、この Codelab で使用したアプリではコンパイルされません)。

val navController = TestNavHostController(
    ApplicationProvider.getApplicationContext()
)

val exampleFragmentScenario = launchFragmentInContainer<ExampleFragment>(themeResId =
R.style.Theme_Example)

exampleFragmentScenario.onFragment { fragment ->

   navController.setGraph(R.navigation.example_nav_graph)

   Navigation.setViewNavController(fragment.requireView(), navController)
}

10 回繰り返すには多くのコードが必要になりますが、JUnit が提供する @Before アノテーションを使用することで、手間を省くことができます。メソッドにこのアノテーションを付けて、そこでテストの設定に必要なコードを指定します。メソッドには任意の名前を付けることができますが、関連性のある名前にする必要があります。同じフラグメントを 10 回設定する代わりに、次のようにセットアップ コードを一度に記述できます。

lateinit var navController: TestNavHostController

lateinit var exampleFragmentScenario: FragmentScenario<ExampleFragment>

@Before
fun setup(){
    navController = TestNavHostController(
        ApplicationProvider.getApplicationContext()
    )

    exampleFragmentScenario =  launchFragmentInContainer(themeResId=R.style.Theme_Example)

    exampleFragmentScenario.onFragment { fragment ->

       navController.setGraph(R.navigation.example_nav_graph)

       Navigation.setViewNavController(fragment.requireView(),  navController)
    }
}

このメソッドは、このクラスで作成するすべてのテストで実行され、どのテストからも必要な変数にアクセスできます。

同様に、各テストの後に実行する必要があるコードがある場合は、@After アノテーションを使用できます。たとえば、@After を使用して、テストに使用したリソースをクリーンアップできるほか、インストルメンテーション テストでデバイスを特定の状態に戻すこともできます。

JUnit には、@BeforeClass アノテーションと @AfterClass アノテーションもあります。このアノテーションを含むメソッドは 1 回だけ実行されますが、実行されたコードはすべてのメソッドに適用されます。セットアップ メソッドやティアダウン メソッドに負荷の高いオペレーションが含まれる場合は、代わりにこれらのアノテーションを使用することをおすすめします。@BeforeClass アノテーションと @AfterClass アノテーションが付いたメソッドはコンパニオン オブジェクトに配置し、@JvmStatic アノテーションを付ける必要があります。これらのアノテーションの実行順を確認するため、次のコードを見てみましょう。

5157ab00a9b7fb84.png

@BeforeClass はクラスに対して実行され、@Before は関数の前に実行され、@After は関数の後に実行され、@AfterClass はクラスに対して実行されます。出力がどのようになるか予想してみてください。

39c04aa2ba7b8348.png

関数の実行順序は setupClass()setupFunction()test_a()tearDownFunction()setupFunction()test_b()tearDownFunction()setupFunction()test_c()tearDownFunction()tearDownClass() です。@Before@After はそれぞれメソッドの前後に実行されるため、これは理にかなっています。@BeforeClass はクラス内のその他すべてが実行される前に 1 回実行され、@AfterClass はクラス内のその他すべてが実行された後に 1 回実行されます。

9. 完了

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

  • Navigation コンポーネントをテストする方法
  • @Before@BeforeClass@After@AfterClass アノテーションを使ってコードの繰り返しを回避する方法