Bibliotheken und Tools zum Testen verschiedener Bildschirmgrößen

Android bietet eine Vielzahl von Tools und APIs, mit denen Sie Tests für verschiedene Bildschirm- und Fenstergrößen erstellen können.

DeviceConfigurationOverride

Mit der zusammensetzbaren Funktion DeviceConfigurationOverride können Sie Konfigurationsattribute überschreiben, um mehrere Bildschirm- und Fenstergrößen in Compose-Layouts zu testen. Die ForcedSize-Überschreibung passt sich an jedes Layout im verfügbaren Bereich an. So können Sie beliebige UI-Tests auf jeder Bildschirmgröße ausführen. Sie können beispielsweise einen kleinen Smartphone-Formfaktor verwenden, um alle UI-Tests auszuführen, einschließlich UI-Tests für große Smartphones, Faltgeräte und Tablets.

   DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
Abbildung 1. Verwendung von „DeviceConfigurationOverride“, um ein Tablet-Layout an ein Gerät mit kleinerem Formfaktor anzupassen, wie in \*Now in Android*.

Außerdem können Sie mit dieser Composable-Funktion die Schriftartskalierung, Designs und andere Eigenschaften festlegen, die Sie bei verschiedenen Fenstergrößen testen möchten.

Robolectric

Mit Robolectric können Sie Compose- oder viewbasierte UI-Tests auf der JVM lokal ausführen. Dazu sind keine Geräte oder Emulatoren erforderlich. Sie können Robolectric konfigurieren, um bestimmte Bildschirmgrößen und andere nützliche Eigenschaften zu verwenden.

Im folgenden Beispiel aus Now in Android ist Robolectric so konfiguriert, dass eine Bildschirmgröße von 1.000 × 1.000 dp mit einer Auflösung von 480 dpi emuliert wird:

@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 { ... }

Sie können die Qualifizierer auch im Testkörper festlegen, wie in diesem Snippet aus dem Beispiel Now in Android zu sehen ist:

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

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

Mit RuntimeEnvironment.setQualifiers() werden die System- und Anwendungsressourcen mit der neuen Konfiguration aktualisiert, es werden jedoch keine Aktionen für aktive Aktivitäten oder andere Komponenten ausgelöst.

Weitere Informationen finden Sie in der Robolectric-Dokumentation unter Device Configuration.

Von Gradle verwaltete Geräte

Mit dem Android-Gradle-Plug-in Von Gradle verwaltete Geräte (Gradle-managed devices, GMD) können Sie die Spezifikationen der Emulatoren und echten Geräte definieren, auf denen Ihre instrumentierten Tests ausgeführt werden. Erstellen Sie Spezifikationen für Geräte mit unterschiedlichen Bildschirmgrößen, um eine Teststrategie zu implementieren, bei der bestimmte Tests auf bestimmten Bildschirmgrößen ausgeführt werden müssen. Wenn Sie GMD mit Continuous Integration (CI) verwenden, können Sie dafür sorgen, dass die entsprechenden Tests bei Bedarf ausgeführt werden, Emulatoren bereitgestellt und gestartet werden und Ihre CI-Einrichtung vereinfacht wird.

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

Im Projekt testing-samples finden Sie mehrere Beispiele für GMD.

Firebase Test Lab

Verwenden Sie Firebase Test Lab (FTL) oder einen ähnlichen Dienst für Geräteparks, um Ihre Tests auf bestimmten echten Geräten auszuführen, auf die Sie möglicherweise keinen Zugriff haben, z. B. auf faltbaren Geräten oder Tablets unterschiedlicher Größe. Firebase Test Lab ist ein kostenpflichtiger Dienst mit einer kostenlosen Stufe. FTL unterstützt auch das Ausführen von Tests auf Emulatoren. Diese Dienste verbessern die Zuverlässigkeit und Geschwindigkeit von instrumentierten Tests, da sie Geräte und Emulatoren im Voraus bereitstellen können.

Informationen zur Verwendung von FTL mit GMD finden Sie unter Tests mit Gradle-verwalteten Geräten skalieren.

Filterung mit dem Testrunner testen

Bei einer optimalen Teststrategie wird nicht zweimal dasselbe geprüft. Daher müssen die meisten Ihrer UI-Tests nicht auf mehreren Geräten ausgeführt werden. Normalerweise filtern Sie Ihre UI-Tests, indem Sie alle oder die meisten davon auf einem Smartphone-Formfaktor und nur eine Teilmenge auf Geräten mit unterschiedlichen Bildschirmgrößen ausführen.

Sie können bestimmte Tests so annotieren, dass sie nur auf bestimmten Geräten ausgeführt werden, und dann ein Argument an AndroidJUnitRunner übergeben, indem Sie den Befehl verwenden, mit dem die Tests ausgeführt werden.

Sie können beispielsweise verschiedene Anmerkungen erstellen:

annotation class TestExpandedWidth
annotation class TestCompactWidth

Und verwenden Sie sie für verschiedene Tests:

class MyTestClass {

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

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

}

Sie können dann das Attribut android.testInstrumentationRunnerArguments.annotation verwenden, um bestimmte Tests zu filtern. Wenn Sie beispielsweise von Gradle verwaltete Geräte verwenden:

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

Wenn Sie GMD nicht verwenden und Emulatoren in der CI verwalten, müssen Sie zuerst dafür sorgen, dass der richtige Emulator oder das richtige Gerät bereit und verbunden ist. Übergeben Sie dann den Parameter an einen der Gradle-Befehle, um instrumentierte Tests auszuführen:

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

Mit Espresso Device (siehe nächsten Abschnitt) können Tests auch anhand von Geräteeigenschaften gefiltert werden.

Espressogerät

Mit Espresso Device können Sie Aktionen auf Emulatoren in Tests mit beliebigen Arten von Instrumentierungstests ausführen, einschließlich Espresso-, Compose- oder UI Automator-Tests. Dazu gehören beispielsweise das Festlegen der Bildschirmgröße oder das Umschalten zwischen den verschiedenen Zuständen oder Positionen des Falt-Displays. Sie können beispielsweise einen faltbaren Emulator steuern und in den Tischmodus versetzen. Espresso Device enthält auch JUnit-Regeln und ‑Annotationen, um bestimmte Funktionen zu erzwingen:

@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 befindet sich noch in der Alphaphase und hat die folgenden Anforderungen:

  • Android-Gradle-Plug-in 8.3 oder höher
  • Android Emulator 33.1.10 oder höher
  • Virtuelles Android-Gerät mit API-Level 24 oder höher

Tests filtern

Espresso Device kann die Eigenschaften verbundener Geräte lesen, damit Sie Tests mit Annotationen filtern können. Wenn die annotierten Anforderungen nicht erfüllt sind, werden die Tests übersprungen.

RequiresDeviceMode-Annotation

Die Annotation RequiresDeviceMode kann mehrmals verwendet werden, um einen Test anzugeben, der nur ausgeführt wird, wenn alle DeviceMode-Werte auf dem Gerät unterstützt werden.

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-Annotation

Mit der Annotation RequiresDisplay können Sie die Breite und Höhe des Gerätebildschirms mithilfe von Größenklassen angeben, die Dimensionsbereiche gemäß den offiziellen Fenstergrößenklassen definieren.

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

Größe von Displays ändern

Verwenden Sie die Methode setDisplaySize(), um die Abmessungen des Bildschirms zur Laufzeit zu ändern. Verwenden Sie die Methode in Verbindung mit der Klasse DisplaySizeRule, um sicherzustellen, dass alle Änderungen, die während der Tests vorgenommen wurden, vor dem nächsten Test rückgängig gemacht werden.

@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.
    }
}

Wenn Sie die Größe eines Displays mit setDisplaySize() ändern, hat das keine Auswirkungen auf die Dichte des Geräts. Wenn eine Dimension nicht auf das Zielgerät passt, schlägt der Test mit einem UnsupportedDeviceOperationException fehl. Wenn Sie verhindern möchten, dass Tests in diesem Fall ausgeführt werden, verwenden Sie die Annotation RequiresDisplay, um sie herauszufiltern:

@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

Die Klasse StateRestorationTester wird verwendet, um die Statuswiederherstellung für zusammensetzbare Komponenten zu testen, ohne Aktivitäten neu zu erstellen. Dadurch werden Tests schneller und zuverlässiger, da das Neuerstellen von Aktivitäten ein komplexer Prozess mit mehreren Synchronisationsmechanismen ist:

@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-Bibliothek

Die Window Testing-Bibliothek enthält Hilfsprogramme, mit denen Sie Tests schreiben können, die auf Funktionen im Zusammenhang mit der Fensterverwaltung basieren oder diese überprüfen, z. B. Activity Embedding oder Funktionen für faltbare Geräte. Das Artefakt ist über das Maven-Repository von Google verfügbar.

Sie können beispielsweise die Funktion FoldingFeature() verwenden, um eine benutzerdefinierte FoldingFeature zu generieren, die Sie in Compose-Vorschauen verwenden können. Verwenden Sie in Java die Funktion createFoldingFeature().

In einer Compose-Vorschau können Sie FoldingFeature so implementieren:

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

Außerdem können Sie mit der Funktion TestWindowLayoutInfo() Displayfunktionen in UI-Tests emulieren. Im folgenden Beispiel wird ein FoldingFeature mit einem HALF_OPENED-Scharnier in der Mitte des Displays simuliert und dann geprüft, ob das Layout dem erwarteten entspricht:

Schreiben

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

Aufrufe

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

Weitere Beispiele finden Sie im WindowManager-Projekt.

Zusätzliche Ressourcen

Dokumentation

Produktproben

Codelabs