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 in „Compose“-Layouts mehrere Bildschirm- und Fenstergrößen zu testen. Die Überschreibung ForcedSize passt in jedes Layout im verfügbaren Bereich, sodass Sie einen UI-Test für jede Bildschirmgröße ausführen können. Sie können beispielsweise einen kleinen Smartphone-Formfaktor verwenden, um alle UI-Tests auszuführen, einschließlich UI-Tests für große Smartphones, faltbare Gerä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. Verwenden von DeviceConfigurationOverride, um ein Tablet-Layout in ein Gerät mit einem kleineren Formfaktor anzupassen, wie in \*Now in Android*.

Außerdem können Sie mit dieser zusammensetzbaren Funktion Schriftskalierung, Designs und andere Eigenschaften festlegen, die Sie an verschiedenen Fenstergrößen testen möchten.

Robolektrik

Mit Robolectric können Sie Compose- oder ansichtsbasierte UI-Tests lokal auf der JVM ausführen, ohne dass Geräte oder Emulatoren erforderlich sind. Sie können Robolectric so konfigurieren, dass neben anderen nützlichen Eigenschaften auch bestimmte Bildschirmgrößen verwendet werden.

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

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

Du kannst die Kennzeichner auch im Testtext festlegen, wie in diesem Snippet aus dem Beispiel Now in Android gezeigt:

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

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

Beachten Sie, dass RuntimeEnvironment.setQualifiers() die System- und Anwendungsressourcen mit der neuen Konfiguration aktualisiert, aber keine Aktion für aktive Aktivitäten oder andere Komponenten auslöst.

Weitere Informationen finden Sie in der Dokumentation zur Gerätekonfiguration von Robolectric.

Von Gradle verwaltete Geräte

Mit dem Android-Gradle-Plug-in Gradle-managed devices (GMD) können Sie die Spezifikationen der Emulatoren und der realen Geräte definieren, auf denen Ihre instrumentierten Tests ausgeführt werden. Erstelle Spezifikationen für Geräte mit unterschiedlichen Bildschirmgrößen, um eine Teststrategie zu implementieren, bei der bestimmte Tests für bestimmte Bildschirmgrößen ausgeführt werden müssen. Wenn Sie GMD mit Continuous Integration (CI) verwenden, können Sie dafür sorgen, dass die richtigen Tests bei Bedarf ausgeführt werden, Emulatoren bereitgestellt und gestartet werden und die 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 GMD-Beispiele.

Firebase Test Lab

Verwende Firebase Test Lab (FTL) oder einen ähnlichen Device Farm-Dienst, um deine Tests auf bestimmten realen Geräten durchzuführen, auf die du möglicherweise keinen Zugriff hast, z. B. faltbare Smartphones 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 Test-Runner testen

Bei einer optimalen Teststrategie sollte nicht dasselbe Ergebnis zweimal überprüft werden. Deshalb müssen die meisten UI-Tests nicht auf mehreren Geräten ausgeführt werden. In der Regel filtern Sie Ihre UI-Tests, indem Sie alle oder die meisten davon auf einem Smartphone-Formfaktor und nur einen Teil auf Geräten mit unterschiedlichen Bildschirmgrößen ausführen.

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

Du kannst beispielsweise verschiedene Anmerkungen erstellen:

annotation class TestExpandedWidth
annotation class TestCompactWidth

Und verwenden Sie sie in verschiedenen Tests:

class MyTestClass {

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

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

}

Sie können dann beim Ausführen der Tests das Attribut android.testInstrumentationRunnerArguments.annotation verwenden, um bestimmte 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 auf CI verwalten, prüfen Sie zuerst, ob der richtige Emulator oder das richtige Gerät bereit und verbunden ist. Übergeben Sie den Parameter dann an einen der Gradle-Befehle, um instrumentierte Tests auszuführen:

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

Beachten Sie, dass das Espresso-Gerät (siehe nächster Abschnitt) auch Tests mithilfe von Geräteeigenschaften filtern kann.

Espressogerät

Verwenden Sie Espresso Device, um Aktionen auf Emulatoren in Tests mit beliebigen instrumentierten Tests auszuführen, einschließlich Espresso-, Compose- oder UI Automator-Tests. Zu diesen Aktionen kann das Einstellen der Bildschirmgröße oder das Umschalten des faltbaren Status oder des faltbaren Zustands gehören. Sie können beispielsweise einen Emulator für faltbare Geräte steuern und in den Modus „Auf dem Tisch“ setzen. Espresso Device enthält auch JUnit-Regeln und -Annotationen, die bestimmte Funktionen erfordern:

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

Beachten Sie, dass sich Espresso Device noch in der Alphaphase befindet und die folgenden Anforderungen erfüllt:

  • 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 mithilfe von Annotationen filtern können. Wenn die annotierten Anforderungen nicht erfüllt sind, werden die Tests übersprungen.

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

ErfordertDisplay-Anmerkung

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

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

Größe von Bildschirmen anpassen

Mit der Methode setDisplaySize() können Sie die Größe des Bildschirms während der Laufzeit anpassen. Verwenden Sie die Methode in Verbindung mit der Klasse DisplaySizeRule, mit der alle während eines Tests vorgenommenen Änderungen 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 du die Größe eines Bildschirms mit setDisplaySize() änderst, hat dies keinen Einfluss auf die Dichte des Geräts. Wenn also eine Dimension nicht in das Zielgerät passt, schlägt der Test mit einem UnsupportedDeviceOperationException fehl. Wenn Sie verhindern möchten, dass in diesem Fall Tests ausgeführt werden, filtern Sie diese mit der Annotation RequiresDisplay heraus:

@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 die Wiederherstellung von Aktivitäten ein komplexer Prozess mit mehreren Synchronisierungsmechanismen 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()
}

Fenstertestbibliothek

Die Window Testing-Bibliothek enthält Dienstprogramme, mit denen Sie Tests schreiben können, die auf Funktionen der Fensterverwaltung beruhen oder diese prüfen, z. B. die Einbettung von Aktivitäten oder faltbare Funktionen. Das Artefakt ist über das Maven-Repository von Google verfügbar.

Beispielsweise haben Sie mit der Funktion FoldingFeature() die Möglichkeit, ein benutzerdefiniertes FoldingFeature-Element zu generieren, das Sie in der Vorschau von „Compose“ verwenden können. Verwenden Sie in Java die Funktion createFoldingFeature().

In einer Vorschau vom Typ „Schreiben“ 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)))
        )
 }

Mit der Funktion TestWindowLayoutInfo() können Sie außerdem Anzeigefeatures in UI-Tests emulieren. Im folgenden Beispiel wird ein FoldingFeature mit einem vertikalen Scharnier HALF_OPENED in der Bildschirmmitte simuliert. Anschließend wird geprüft, ob das Layout dem erwarteten Layout entspricht:

Textvorschläge

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