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

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

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

特に、アクティビティのライフサイクルで説明されているイベントに対してアクティビティが正しく動作するようにすることが重要です。

このガイドでは、アプリのアクティビティがライフサイクルのさまざまな状態に遷移するときに、データの整合性と優れたユーザー エクスペリエンスを維持するアプリの能力を評価する方法について説明します。

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

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

ActivityScenario は、ローカル単体テストとデバイス上の統合テストで同様に使用できるクロス プラットフォーム API です。実際のデバイスまたは仮想デバイスでは、ActivityScenario はスレッドセーフを提供し、テストのインストルメンテーション スレッドとテスト対象のアクティビティを実行するスレッドの間でイベントを同期します。

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

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

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

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

アクティビティの作成後、ActivityScenario はアクティビティを RESUMED 状態に移行します。この状態は、アクティビティが実行中で、ユーザーに表示されていることを示します。この状態では、Espresso UI テストを使用してアクティビティの View 要素を自由に操作できます。

テストの完了時に、アクティビティで close を呼び出すことをおすすめします。これにより、関連リソースがクリーンアップされ、テストの安定性が向上します。ActivityScenarioCloseable を実装しているため、use 拡張(または Java プログラミング言語の try-with-resources)を適用して、アクティビティを自動的に閉じることができます。

または、ActivityScenarioRule を使用して、各テストの前に ActivityScenario.launch と、テストの破棄時に ActivityScenario.close を自動的に呼び出すこともできます。次の例は、ルールを定義し、ルールからシナリオのインスタンスを取得する方法を示しています。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @get:Rule var activityScenarioRule = activityScenarioRule<MyActivity>()

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

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

アクティビティを別の状態(CREATEDSTARTED など)にするには、moveToState() を呼び出します。このアクションは、アクティビティが別のアプリまたはシステム アクションによって中断され、それぞれが停止または一時停止される状況をシミュレートします。

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

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

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

テスト対象のアクティビティの現在の状態を特定するには、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 フィールドの値を取得します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testResult() {
        launchActivity<MyActivity>().use {
            onView(withId(R.id.finish_button)).perform(click())

            // Activity under test is now finished.

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

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

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

テスト対象のアクティビティでアクションをトリガーするには、Espresso ビュー マッチャーを使用してビュー内の要素を操作します。

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use {
            onView(withId(R.id.refresh)).perform(click())
        }
    }
}

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

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