アプリのアクティビティをテストする

アクティビティは、アプリ内ですべてのユーザー インタラクションのコンテナとして機能します。そのため、次のようなデバイスレベルのイベント中に、アプリのアクティビティがどのように動作するかをテストすることが重要です。

  • デバイスの電話アプリなど、別のアプリがアプリのアクティビティに割り込む。
  • システムによってアクティビティが破棄され、再作成される。
  • ユーザーが、ピクチャー イン ピクチャー(PIP)やマルチウィンドウなどの新しいウィンドウ環境にアクティビティを配置する。

特に、アクティビティのライフサイクルに記載されているイベントに応じて、アクティビティが正しく動作するのを 確認することが重要です。

このガイドでは、ライフサイクルの各段階を経てアプリの Activity が遷移する際に、データの完全性と優れたユーザー エクスペリエンスを維持するアプリの機能を評価する方法について説明します。

Compose でアクティビティをテストする

Jetpack Compose でビルドされたアプリをテストする場合、通常は createAndroidComposeRuleを使用してアクティビティを起動し、 UI コンポーネントを操作します。

ただし、構成の変更、アクティビティがバックグラウンドに置かれる、システムによって破棄されるなどのデバイスレベルのイベントをテストするには、アクティビティのライフサイクルを直接操作する必要があります。これを行うには、 基盤となる ActivityScenario フレームワークを使用します。

Compose テストルールは、このシナリオを自動的にラップして管理します。 このガイドでは、最新の UI テストと標準のライフサイクル管理のギャップを埋めるために、次のパターンを使用します。

@get:Rule
val composeTestRule = createAndroidComposeRule<MyActivity>()

@Test fun testEvent() {
    val scenario = composeTestRule.activityRule.scenario

    // ...
}

アクティビティの状態を遷移させる

アプリのアクティビティをテストする際に重要なことの 1 つは、アプリのアクティビティを特定の状態に置くことに関連します。テストの「特定の状態」を定義するには、 ActivityScenarioのインスタンスを使用します。これは、AndroidX Test ライブラリの一部です。このクラスを使用すると、デバイスレベルのイベントをシミュレートする状態にアクティビティを配置できます。

ActivityScenario は、ローカル ユニットのテストとデバイス上での統合テストで同様に使用できるクロス プラットフォーム API です。実際のデバイスまたは仮想デバイスで、ActivityScenario によってスレッドの安全性が実現し、テストのインストルメンテーション スレッドとテスト中のアクティビティを実行するスレッド間でイベントの同期が行われます。

この API は、テスト対象のアクティビティが破棄または作成された場合に、どのように動作するかを評価するのにも非常に適しています。本セクションでは、この API に関連する最も一般的なユースケースを示します。

アクティビティを作成する

テスト対象のアクティビティを作成するには、次のスニペットに示すコードを追加します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
       launchActivity<MyActivity>().use {
       }
    }
}

アクティビティを作成すると、アクティビティは ActivityScenario によって RESUMED の状態に移行します。この状態はアクティビティが実行中で、ユーザーに表示されていることを示します。この状態では、Compose テスト API を使用してアクティビティの コンポーザブルを自由に操作できます。

テストが完了したら、アクティビティで close を呼び出すことをおすすめします。 これにより、関連するリソースがクリーンアップされ、テストの安定性が向上します。ActivityScenarioCloseable を実装しているため、use 拡張機能を適用してアクティビティを自動的に閉じることができます。

または、createAndroidComposeRule を使用して、各テストの前にアクティビティを自動的に 起動し、ティアダウンを処理して、Compose UI テストメソッドと基盤となる ActivityScenario の両方にアクセスできるようにすることもできます。以下の例では、ルールを定義して、そのルールからシナリオのインスタンスを取得する方法を示しています。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @Test fun testEvent() {
        val scenario = composeTestRule.activityRule.scenario
    }
}

アクティビティを新しい状態に遷移させる

アクティビティを CREATEDSTARTED などの別の状態に遷移させるには、moveToState を呼び出します。この操作は、別のアプリまたはシステム操作の割り込みによって、アクティビティが停止または一時停止される場合の状況をシミュレートします。

moveToState の使用例を次のコード スニペットに示します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.moveToState(State.CREATED)
        }
    }
}

現在のアクティビティの状態を確認する

テスト対象の Activity の現在の状態を確認するには、ActivityScenario オブジェクト内の state フィールドの値を取得します。次のコード スニペットで示すように、アクティビティが終了した場合もしくは別のアクティビティにリダイレクトされた場合に、テスト対象のアクティビティの状態を確認する際に便利です。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.onActivity { activity ->
              startActivity(Intent(activity, MyOtherActivity::class.java))
            }

            val originalActivityState = scenario.state
        }
    }
}

アクティビティを再作成する

デバイスのリソースが不足していると、システムがアクティビティを破棄することがあり、その場合、ユーザーがアプリに戻った際にアプリがアクティビティを再作成する必要が生じます。これらの条件をシミュレートするには、recreate を呼び出します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.recreate()
        }
    }
}

ActivityScenario クラスは、アクティビティの保存済みインスタンスの状態と、@NonConfigurationInstance アノテーションが付けられたオブジェクトを保持します。これらのオブジェクトは、テスト対象のアクティビティの新しいインスタンスに読み込まれます。

アクティビティの検索結果を取得する

完成したアクティビティに関連付けられた結果のコードまたはデータを取得するには、ActivityScenario オブジェクト内の result フィールドの値を取得します。次のコード スニペットに示すように、createAndroidComposeRule を使用すると、アクティビティを終了する UI アクションを簡単にトリガーできます。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @Test fun testResult() {
        composeTestRule.onNodeWithTag("finish_button").performClick()

        val scenario = composeTestRule.activityRule.scenario
        val resultCode = scenario.result.resultCode
        val resultData = scenario.result.resultData
    }
}

アクティビティでアクションをトリガーする

ActivityScenario 内のすべてのメソッドはブロッキング呼び出しであるため、API ではインストルメンテーション スレッドで実行する必要があります。

テスト対象のアクティビティでアクションをトリガーするには、Compose テスト API を使用してコンポーザブルを操作します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @Test fun testEvent() {
        composeTestRule.onNodeWithText("Refresh").performClick()
    }
}

ただし、アクティビティ自体でメソッドを呼び出す必要がある場合は、onActivity を使用することで安全に実行できます。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.onActivity { activity ->
              activity.handleSwipeToRefresh()
            }
        }
    }
}

参考情報

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

ドキュメント