Register now for Android Dev Summit 2019!

アプリのフラグメントをテストする

フラグメントはアプリ内で再利用可能なコンテナとして機能するので、さまざまなアクティビティやレイアウト設定で同じユーザー インターフェース レイアウトを表示することができます。 これらのフラグメントの汎用性が高いことを考えると、一貫性のあるリソース効率の高いエクスペリエンスを提供しているものになっているかどうかを検証することはとても大切です。

  • サイズの大きい画面や端末の横向き画面をサポートする設定など、さまざまなレイアウト設定の間で、フラグメントの表示の一貫性が保たれている必要があります。
  • フラグメントがユーザーに表示されないときは、フラグメントのビュー階層を作成しないでください。

この文書では、各フラグメントの動作を評価するフレームワーク提供の API をテストに組み込む方法を説明します。

フラグメントの状態を変更する

このテストを実行する条件を設定するために、AndroidX ではフラグメントを作成して状態を変更する FragmentScenario というライブラリが提供されています。

テスト アーティファクト ロケーションを設定する

FragmentScenario を意図したとおりに使用するため、次のコード スニペットに示されているように、フラグメントテスト アーティファクトをアプリのテスト APK に定義します。

app/build.gradle

dependencies {
    // ...
    debugImplementation 'androidx.fragment:fragment-testing:1.1.0-alpha07'
}

フラグメントを作成する

FragmentScenario には、次のタイプのフラグメントを起動するメソッドが組み込まれています。

それらのメソッドは次のタイプのフラグメントもサポートしています。

  • グラフィカル フラグメント。この中にユーザー インタフェースが含まれます。 このタイプのフラグメントを起動するには、launchFragmentInContainer() を呼び出します。 FragmentScenario は、このフラグメントをアクティビティのルート ビュー コントローラにアタッチします。 フラグメントがアタッチされなければ、このアクティビティは空です。
  • 非グラフィカル フラグメントヘッドレス フラグメントということもある)。複数のアクティビティに組み込まれている情報の格納や短期処理を行います。 このタイプのフラグメントを起動するには、launchFragment() を呼び出します。 FragmentScenario は、このタイプのフラグメントをルートビューがない完全に空のアクティビティに添付します。

これらのフラグメント タイプのいずれかを起動すると、FragmentScenario はテスト中のフラグメントの状態を RESUMED に変更します。 この状態はフラグメントが実行中であることを表します。 グラフィカル フラグメントのテストを行っている場合、ユーザーはテスト中の様子を見ることもできるので、Espresso UI テストを使用して、このフラグメントの UI 要素に関する情報を評価することができます。

次のコード スニペットは、各タイプのフラグメントを起動する方法を示しています。

グラフィカル フラグメントの例

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "state" and "factory" arguments are optional.
        val fragmentArgs = Bundle().apply {
            putInt("selectedListItem", 0)
        }
        val factory = MyFragmentFactory()
        val scenario = launchFragmentInContainer<MyFragment>(
                fragmentArgs, factory)
        onView(withId(R.id.text)).check(matches(withText("Hello World!")))
    }
}

非グラフィカル フラグメントの例

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "state" and "factory" arguments are optional.
        val fragmentArgs = Bundle().apply {
            putInt("numElements", 0)
        }
        val factory = MyFragmentFactory()
        val scenario = launchFragment<MyFragment>(fragmentArgs, factory)
    }
}

フラグメントを再作成する

デバイスのリソースが不足しているとき、フラグメントを含むアクティビティがシステムによって削除されることがあります。そのようなタイミングでユーザーがアプリを再び表示した場合、アプリが元のフラグメントを再作成することが必要になります。 この状況をシミュレーションするには、recreate() を呼び出します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        scenario.recreate()
    }
}

FragmentScenario クラスがテスト中のフラグメントを再作成すると、フラグメントは再作成前のライフサイクル状態に戻ります。

フラグメントを新しい状態に変更する

ほとんどの場合、アプリの UI テストでは、テスト中のフラグメントを起動して再作成すれば十分です。 ただし詳細なユニットテストを行っている場合は、あるライフサイクル状態から別のライフサイクル状態に遷移するときのフラグメントの動作を評価すると良いかもしれません。

フラグメントを別のライフサイクル状態に変更するには、moveToState() を呼び出します。 このメソッドは、CREATEDSTARTEDRESUMEDDESTROYED の状態を引数としてサポートします。 この処理は、フラグメントを含むアクションが別のアプリやシステム処理によって中断させられて、アクティビティの状態が変更になった状況をシミュレーションしています。

moveToState() の使用例が次のコードスニペットに示されています。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        scenario.moveToState(State.CREATED)
    }
}

フラグメント内でのアクションのトリガー

テスト中のフラグメント内でアクションをトリガーするには、Espresso ビュー マッチャーを使用してビュー内の要素と対話します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        onView(withId(R.id.refresh))
                .perform(click())
    }
}

オプション メニューの選択に応答するなど、フラグメント自体のメソッドを呼び出す必要がある場合は、次のように FragmentAction を実装すれば安全に実行できます。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        scenario.onFragment(fragment ->
            fragment.onOptionsItemSelected(clickedItem) {
                // Update fragment's state based on selected item.
            }
        }
    }
}