可用來測試不同螢幕大小的程式庫和工具

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 或 View 型 UI 測試,無需裝置或模擬器。您可以設定 Robolectric 使用特定螢幕大小和其他實用屬性。

在下列 Now in Android 中的範例中,Robolectric 設為模擬 1000x1000 dp 的螢幕大小,且解析度為 480 dpi:

@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) 搭配使用,就能確保在必要時執行適當的測試、佈建及啟動模擬器,以及簡化持續整合設定。

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"
                }
            }
        }
    }
}

您可以在 testing-samples 專案中找到多個 GMD 範例。

Firebase Test Lab

您可以使用 Firebase Test Lab (FTL) 或類似的裝置農場服務,在您可能無法存取的特定實際裝置上執行測試,例如不同大小的折疊式裝置或平板電腦。Firebase Test Lab 是免費方案的付費服務。FTL 也支援在模擬器上執行測試。這些服務可以預先佈建裝置和模擬器,因此可提高檢測設備測試的可靠性和速度。

如要進一步瞭解如何搭配 GMD 使用 FTL,請參閱「使用 Gradle 管理的裝置擴充測試」。

使用測試執行器測試篩選功能

最佳測試策略不應驗證相同項目兩次,因此大部分 UI 測試都不需要在多部裝置上執行。一般而言,您可以在手機板型規格上執行全部或大部分 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 裝置 (請參閱下一節) 也可以使用裝置屬性篩選測試。

Espresso 裝置

使用 Espresso Device,即可在測試中使用任何類型的檢測設備測試 (包括 Espresso、Compose 或 UI Automator 測試) 在模擬器中執行操作。這些動作可能包括設定螢幕大小或切換摺疊式裝置的狀態/型態。舉例來說,您可以控制折疊式模擬器並將其設為桌面模式。Espresso 裝置還包含需要特定功能的 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 仍為 Alpha 版階段,並有下列需求條件:

  • Android Gradle 外掛程式 8.3 以上版本
  • Android Emulator 33.1.10 以上版本
  • 搭載 API 級別 24 以上版本的 Android 虛擬裝置

篩選測試

Espresso 裝置可以讀取已連結裝置的屬性,方便您使用註解篩選測試。如未符合加註的要求,系統就會略過測試。

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 測試程式庫包含的公用程式可協助您編寫依賴或驗證與視窗管理相關功能 (例如活動嵌入或折疊式功能) 的測試。該構件可透過 Google 的 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)))
        )
 }

此外,您也可以使用 TestWindowLayoutInfo() 函式,在 UI 測試中模擬顯示功能。以下範例會模擬 FoldingFeature,其螢幕中心有一個 HALF_OPENED 垂直轉軸,然後檢查此版面配置是否符合預期:

Compose

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()
    }
}

View

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 專案中找到更多範例。

其他資源

說明文件

範例

程式碼研究室