さまざまな画面サイズをテストするためのライブラリとツール

Android には、さまざまな画面サイズとウィンドウ サイズのテストを作成するのに役立つさまざまなツールと API が用意されています。

DeviceConfigurationOverride

DeviceConfigurationOverride コンポーズ可能関数を使用すると、構成属性をオーバーライドして、Compose レイアウトで複数の画面サイズとウィンドウ サイズをテストできます。ForcedSize オーバーライドは、使用可能なスペースにレイアウトを合わせるため、 あらゆる画面サイズで任意の UI テストを実行できます。たとえば、小型のスマートフォン フォーム ファクタを使用して、大型のスマートフォン、折りたたみ式デバイス、タブレットの UI テストなど、すべての UI テストを実行できます。

   DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
図 1.DeviceConfigurationOverride を使用して、*Now in Android* のように、タブレット レイアウトを小型のフォーム ファクタ デバイスに合わせます。

また、このコンポーズ可能関数を使用して、さまざまなウィンドウ サイズでテストするフォント スケール、テーマ、その他のプロパティを設定することもできます。

Robolectric

Robolectric を使用すると、デバイスやエミュレータを使用せずに、ローカルの JVM で Compose ベースまたはビューベースの UI テストを実行できます。Robolectric を構成して、特定の画面サイズを使用できます。

次の は、Now in Android の例です。Robolectric は、解像度 480 dpi の 1000x1000 dp の画面サイズをエミュレートするように 構成されています。

@RunWith(RobolectricTestRunner::class)
// Configure Robolectric to use a very large screen size that can fit all of the test sizes.
// This allows enough room to render the content under test without clipping or scaling.
@Config(qualifiers = "w1000dp-h1000dp-480dpi")
class NiaAppScreenSizesScreenshotTests { ... }

また、テスト本文から修飾子を設定することもできます。 Now in Android の例の次のスニペットのようにします。

val (width, height, dpi) = ...

// Set qualifiers from specs.
RuntimeEnvironment.setQualifiers("w${width}dp-h${height}dp-${dpi}dpi")

RuntimeEnvironment.setQualifiers() は、新しい構成でシステムとアプリケーションのリソースを更新しますが、アクティブなアクティビティや他のコンポーネントでアクションをトリガーしません。

詳細については、Robolectric デバイス設定のドキュメントをご覧ください。

Gradle で管理されているデバイス

Gradle で管理されているデバイス(GMD)Android Gradle プラグイン を使用すると、インストルメンテーション化されたテストを実行するエミュレータと実際のデバイスの仕様を定義できます。画面サイズが異なるデバイスの仕様を作成して、特定のテストを特定の画面サイズで実行する必要があるテスト戦略を実装します。GMD を 継続的インテグレーション (CI)で使用すると、必要に応じて適切なテストを実行し、 エミュレータのプロビジョニングと起動を行い、CI の設定を簡素化できます。

android {
    testOptions {
        managedDevices {
            devices {
                // Run with ./gradlew nexusOneApi30DebugAndroidTest.
                nexusOneApi30(com.android.build.api.dsl.ManagedVirtualDevice) {
                    device = "Nexus One"
                    apiLevel = 30
                    // Use the AOSP ATD image for better emulator performance
                    systemImageSource = "aosp-atd"
                }
                // Run with ./gradlew  foldApi34DebugAndroidTest.
                foldApi34(com.android.build.api.dsl.ManagedVirtualDevice) {
                    device = "Pixel Fold"
                    apiLevel = 34
                    systemImageSource = "aosp-atd"
                }
            }
        }
    }
}

GMD の例は、testing-samples プロジェクトに複数あります。

Firebase Test Lab

Firebase Test Lab(FTL)または同様のデバイス ファーム サービスを使用して、アクセスできない可能性のある特定の実際のデバイス(さまざまなサイズの折りたたみ式デバイスやタブレットなど)で テストを実行します。Firebase Test Lab は、有料 サービスです。無料枠があります。FTL は、エミュレータでのテストの実行もサポートしています。 これらのサービスは、デバイスとエミュレータを事前にプロビジョニングできるため、インストルメンテーション化されたテストの信頼性と速度を向上させます。

GMD で FTL を使用する方法については、Gradle で管理されているデバイスを使用したテストのスケーリングをご覧ください。

テスト Runner を使用したテストのフィルタリング

最適なテスト戦略では、同じことを 2 回検証しないようにする必要があります。そのため、ほとんどの UI テストは複数のデバイスで実行する必要はありません。通常、UI テストをフィルタリングするには、スマートフォン フォーム ファクタでテストのすべてまたはほとんどを実行し、画面サイズが異なるデバイスではサブセットのみを実行します。

特定のテストにアノテーションを付けて、特定のデバイスでのみ実行するようにし、テストを実行するコマンドを使用して AndroidJUnitRunner に引数を渡すことができます。

たとえば、次のようにさまざまなアノテーションを作成できます。

annotation class TestExpandedWidth
annotation class TestCompactWidth

これらをさまざまなテストで使用します。

class MyTestClass {

    @Test
    @TestExpandedWidth
    fun myExample_worksOnTablet() {
        ...
    }

    @Test
    @TestCompactWidth
    fun myExample_worksOnPortraitPhone() {
        ...
    }

}

テストの実行時に android.testInstrumentationRunnerArguments.annotation プロパティを使用して、特定のテストをフィルタリングできます。たとえば、Gradle で管理されているデバイスを使用している場合:

$ ./gradlew pixelTabletApi30DebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation='com.sample.TestExpandedWidth'

GMD を使用せず、CI でエミュレータを管理する場合は、まず正しいエミュレータまたはデバイスが準備され、接続されていることを確認してから、パラメータを Gradle コマンドのいずれかに渡してインストルメンテーション化されたテストを実行します。

$ ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation='com.sample.TestExpandedWidth'

Espresso Device(次のセクションを参照)でも、デバイスのプロパティを使用してテストをフィルタリングできます。

Espresso Device

Espresso Device を使用すると、Esp101resso、Compose、UI Automator テストなど、あらゆる種類のインストルメンテーション化されたテストを使用して、テストでエミュレータに対してアクションを実行できます。101これらのアクションには、画面サイズの設定や、折りたたみ式デバイスの状態や形状の切り替えなどがあります。たとえば、折りたたみ式エミュレータを制御して、テーブルトップ モードに設定できます。Espresso Device には、特定の機能を必要とする JUnit ルールとアノテーションも含まれています。

@RunWith(AndroidJUnit4::class)
class OnDeviceTest {

    @get:Rule(order=1) val activityScenarioRule = activityScenarioRule<MainActivity>()

    @get:Rule(order=2) val screenOrientationRule: ScreenOrientationRule =
        ScreenOrientationRule(ScreenOrientation.PORTRAIT)

    @Test
    fun tabletopMode_playerIsDisplayed() {
        // Set the device to tabletop mode.
        onDevice().setTabletopMode()
        onView(withId(R.id.player)).check(matches(isDisplayed()))
    }
}

Espresso Device はまだアルファ版であり、次の 要件があります。

  • Android Gradle プラグイン 8.3 以降
  • Android エミュレータ 33.1.10 以降
  • API レベル 24 以降を実行する Android 仮想デバイス

テストをフィルタリングする

Espresso Device は、接続されているデバイスのプロパティを読み取って、 アノテーションを使用してテストをフィルタリングできます。アノテーション付きの要件が満たされていない場合、テストはスキップされます。

RequiresDeviceMode アノテーション

RequiresDeviceMode アノテーションは複数回使用して、デバイスで DeviceMode 値が すべて サポートされている場合にのみ実行されるテストを示すことができます。

class OnDeviceTest {
    ...
    @Test
    @RequiresDeviceMode(TABLETOP)
    @RequiresDeviceMode(BOOK)
    fun tabletopMode_playerIdDisplayed() {
        // Set the device to tabletop mode.
        onDevice().setTabletopMode()
        onView(withId(R.id.player)).check(matches(isDisplayed()))
    }
}

RequiresDisplay アノテーション

RequiresDisplay アノテーションを使用すると、公式の ウィンドウ サイズクラスに沿ってディメンション バケットを定義する サイズクラスを使用して、デバイス画面の幅と高さを指定できます。

class OnDeviceTest {
    ...
    @Test
    @RequiresDisplay(EXPANDED, COMPACT)
    fun myScreen_expandedWidthCompactHeight() {
        ...
    }
}

ディスプレイのサイズを変更する

setDisplaySize() メソッドを使用して、実行時に画面のサイズを変更します 。このメソッドは、DisplaySizeRule クラスと組み合わせて使用します。これにより、テスト中に行われた変更は 次のテストの前に元に戻されます。

@RunWith(AndroidJUnit4::class)
class ResizeDisplayTest {

    @get:Rule(order = 1) val activityScenarioRule = activityScenarioRule<MainActivity>()

    // Test rule for restoring device to its starting display size when a test case finishes.
    @get:Rule(order = 2) val displaySizeRule: DisplaySizeRule = DisplaySizeRule()

    @Test
    fun resizeWindow_compact() {
        onDevice().setDisplaySize(
            widthSizeClass = WidthSizeClass.COMPACT,
            heightSizeClass = HeightSizeClass.COMPACT
        )
        // Verify visual attributes or state restoration.
    }
}

setDisplaySize() でディスプレイのサイズを変更しても、デバイスの密度には影響しません。そのため、対象デバイスにサイズが合わない場合、テストは UnsupportedDeviceOperationException で失敗します。このような場合にテストが実行されないようにするには、RequiresDisplay アノテーションを使用してフィルタします。

@RunWith(AndroidJUnit4::class)
class ResizeDisplayTest {

    @get:Rule(order = 1) var activityScenarioRule = activityScenarioRule<MainActivity>()

    // Test rule for restoring device to its starting display size when a test case finishes.
    @get:Rule(order = 2) var displaySizeRule: DisplaySizeRule = DisplaySizeRule()

    /**
     * Setting the display size to EXPANDED would fail in small devices, so the [RequiresDisplay]
     * annotation prevents this test from being run on devices outside the EXPANDED buckets.
     */
    @RequiresDisplay(
        widthSizeClass = WidthSizeClassEnum.EXPANDED,
        heightSizeClass = HeightSizeClassEnum.EXPANDED
    )
    @Test
    fun resizeWindow_expanded() {
        onDevice().setDisplaySize(
            widthSizeClass = WidthSizeClass.EXPANDED,
            heightSizeClass = HeightSizeClass.EXPANDED
        )
        // Verify visual attributes or state restoration.
    }
}

StateRestorationTester

StateRestorationTester クラスは、アクティビティを再作成せずに、コンポーズ可能なコンポーネントの状態復元 をテストするために使用されます。アクティビティの再作成は、複数の同期メカニズムを使用する複雑なプロセスであるため、テストの速度と信頼性が向上します。

@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
    val stateRestorationTester = StateRestorationTester(composeTestRule)

    // Set content through the StateRestorationTester object.
    stateRestorationTester.setContent {
        MyApp()
    }

    // Simulate a config change.
    stateRestorationTester.emulateSavedInstanceStateRestore()
}

Window Testing ライブラリ

Window Testing ライブラリには、アクティビティの埋め込みや折りたたみ式デバイスの機能など、ウィンドウマネージャーに関連する機能に依存するテストや、それらの機能を検証するテストを作成するのに役立つユーティリティが含まれています。アーティファクトは、Google's Maven リポジトリから入手できます。

たとえば、FoldingFeature() 関数を使用してカスタム FoldingFeature を生成し、Compose プレビューで使用できます。Java では、 createFoldingFeature() 関数を使用します。

Compose プレビューでは、次のように FoldingFeature を実装できます。

@Preview(showBackground = true, widthDp = 480, heightDp = 480)
@Composable private fun FoldablePreview() =
    MyApplicationTheme {
        ExampleScreen(
            displayFeatures = listOf(FoldingFeature(Rect(0, 240, 480, 240)))
        )
 }

また、UI テストでディスプレイ機能をエミュレートするには、 TestWindowLayoutInfo() 関数を使用することもできます。 次の例では、画面の中央の垂直ヒンジが HALF_OPENED である場合のFoldingFeatureをシミュレートして、 レイアウトが想定どおりであるかを確認します。

作成

import androidx.window.layout.FoldingFeature.Orientation.Companion.VERTICAL
import androidx.window.layout.FoldingFeature.State.Companion.HALF_OPENED
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule

@RunWith(AndroidJUnit4::class)
class MediaControlsFoldingFeatureTest {

    @get:Rule(order=1)
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @get:Rule(order=2)
    val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()

    @Test
    fun foldedWithHinge_foldableUiDisplayed() {
        composeTestRule.setContent {
            MediaPlayerScreen()
        }

        val hinge = FoldingFeature(
            activity = composeTestRule.activity,
            state = HALF_OPENED,
            orientation = VERTICAL,
            size = 2
        )

        val expected = TestWindowLayoutInfo(listOf(hinge))
        windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(expected)

        composeTestRule.waitForIdle()

        // Verify that the folding feature is detected and media controls shown.
        composeTestRule.onNodeWithTag("MEDIA_CONTROLS").assertExists()
    }
}

視聴回数

import androidx.window.layout.FoldingFeature.Orientation
import androidx.window.layout.FoldingFeature.State
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule

@RunWith(AndroidJUnit4::class)
class MediaControlsFoldingFeatureTest {

    @get:Rule(order=1)
    val activityRule = ActivityScenarioRule(MediaPlayerActivity::class.java)

    @get:Rule(order=2)
    val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()

    @Test
    fun foldedWithHinge_foldableUiDisplayed() {
        activityRule.scenario.onActivity { activity ->
            val feature = FoldingFeature(
                activity = activity,
                state = State.HALF_OPENED,
                orientation = Orientation.VERTICAL)
            val expected = TestWindowLayoutInfo(listOf(feature))
            windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(expected)
        }

        // Verify that the folding feature is detected and media controls shown.
        onView(withId(R.id.media_controls)).check(matches(isDisplayed()))
    }
}

その他のサンプルについては、WindowManager プロジェクトをご覧ください。

参考情報

ドキュメント

サンプル

Codelab