Biblioteki i narzędzia do testowania różnych rozmiarów ekranu

Android udostępnia różne narzędzia i interfejsy API, które mogą pomóc w tworzeniu testów na różnych ekranach i w oknach o różnych rozmiarach.

DeviceConfigurationOverride

Element kompozycyjny DeviceConfigurationOverride umożliwia zastępowanie atrybutów konfiguracji w celu testowania różnych rozmiarów ekranu i okna w układach Compose. Zastąpienie ForcedSize dopasowuje dowolny układ do dostępnego miejsca, co pozwala na uruchamianie dowolnych testów interfejsu na ekranach o dowolnym rozmiarze. Możesz na przykład użyć małego telefonu, aby uruchomić wszystkie testy interfejsu, w tym testy interfejsu na dużych telefonach, urządzeniach składanych i tabletach.

   DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
Rysunek 1. Używanie DeviceConfigurationOverride do dopasowania układu tabletu do mniejszego urządzenia, jak w aplikacji *Now in Android*.

Możesz też użyć tego elementu kompozycyjnego, aby ustawić skalę czcionki, motywy i inne właściwości, które chcesz przetestować w oknach o różnych rozmiarach.

Robolectric

Użyj Robolectric, aby uruchamiać testy interfejsu Compose lub opartego na widokach lokalnie w JVM – nie są wymagane żadne urządzenia ani emulatory. Możesz skonfigurować Robolectric tak, aby używał określonych rozmiarów ekranu i innych przydatnych właściwości.

W tym przykładzie z aplikacji Now in Android Robolectric jest skonfigurowany tak, aby emulować ekran o rozmiarze 1000 x 1000 dp i rozdzielczości 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 { ... }

Możesz też ustawić kwalifikatory w treści testu, jak w tym fragmencie kodu z przykładu Now in Android:

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

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

Pamiętaj, że RuntimeEnvironment.setQualifiers() aktualizuje zasoby systemu i aplikacji o nową konfigurację, ale nie wywołuje żadnych działań w aktywnych aktywnościach ani innych komponentach.

Więcej informacji znajdziesz w dokumentacji konfiguracji urządzenia Robolectric Device Configuration.

Urządzenia zarządzane przez Gradle

Wtyczka Androida do obsługi Gradle urządzeń zarządzanych przez Gradle (GMD) umożliwia definiowanie specyfikacji emulatorów i rzeczywistych urządzeń, na których są uruchamiane testy instrumentowane. Utwórz specyfikacje urządzeń o różnych rozmiarach ekranu, aby wdrożyć strategię testowania, w której niektóre testy muszą być uruchamiane na ekranach o określonych rozmiarach. Używając GMD z trybem ciągłej integracji (CI), możesz mieć pewność, że w razie potrzeby zostaną uruchomione odpowiednie testy, a także że emulatory zostaną udostępnione i uruchomione, co uprości konfigurację 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"
                }
            }
        }
    }
}

Wiele przykładów GMD znajdziesz w projekcie testing-samples.

Laboratorium Firebase

Użyj Laboratorium Firebase (FTL) lub podobnej usługi farmy urządzeń, aby uruchamiać swoje testy na określonych rzeczywistych urządzeniach, do których możesz nie mieć dostępu, takich jak urządzenia składane lub tablety o różnych rozmiarach. Laboratorium Firebase to usługa płatna z bezpłatnym pakietem. FTL obsługuje też uruchamianie testów na emulatorach. Te usługi zwiększają niezawodność i szybkość testów instrumentowanych, ponieważ mogą udostępniać urządzenia i emulatory z wyprzedzeniem.

Informacje o używaniu FTL z GMD znajdziesz w artykule Skalowanie testów za pomocą urządzeń zarządzanych przez Gradle.

Filtrowanie testów za pomocą narzędzia do uruchamiania testów

Optymalna strategia testowania nie powinna weryfikować tego samego dwa razy, dlatego większość testów interfejsu nie musi być uruchamiana na wielu urządzeniach. Zazwyczaj filtrujesz testy interfejsu, uruchamiając wszystkie lub większość z nich na telefonie, a tylko podzbiór na urządzeniach o różnych rozmiarach ekranu.

Możesz oznaczyć niektóre testy tak, aby były uruchamiane tylko na określonych urządzeniach, a następnie przekazać argument do AndroidJUnitRunner za pomocą polecenia, które uruchamia testy.

Możesz na przykład utworzyć różne adnotacje:

annotation class TestExpandedWidth
annotation class TestCompactWidth

I używać ich w różnych testach:

class MyTestClass {

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

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

}

Podczas uruchamiania testów możesz użyć właściwości android.testInstrumentationRunnerArguments.annotation, aby filtrować określone testy. Jeśli na przykład używasz urządzeń zarządzanych przez Gradle:

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

Jeśli nie używasz GMD i zarządzasz emulatorami w CI, najpierw upewnij się, że odpowiedni emulator lub urządzenie jest gotowe i połączone, a następnie przekaż parametr do jednego z poleceń Gradle, aby uruchomić testy instrumentowane:

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

Pamiętaj, że Espresso Device (patrz następna sekcja) może też filtrować testy za pomocą właściwości urządzenia.

Espresso Device

Użyj Espresso Device, aby wykonywać działania na emulatorach w testach za pomocą dowolnego typu testów instrumentowanych, w tym testów Espresso, Compose lub UI Automator. Te działania mogą obejmować ustawianie rozmiaru ekranu lub przełączanie stanów i pozycji urządzenia składanego. Możesz na przykład sterować emulatorem urządzenia składanego i ustawić go w trybie stołowym. Espresso Device zawiera też reguły i adnotacje JUnit, które wymagają określonych funkcji:

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

Pamiętaj, że Espresso Device jest nadal w wersji alfa i ma te wymagania:

  • Wtyczka Androida do obsługi Gradle w wersji 8.3 lub nowszej
  • Android Emulator 33.1.10 lub nowszy
  • Wirtualne urządzenie z Androidem z poziomem API 24 lub nowszym

Filtrowanie testów

Espresso Device może odczytywać właściwości połączonych urządzeń, aby umożliwić filtrowanie testów za pomocą adnotacji. Jeśli wymagania oznaczone adnotacjami nie są spełnione, testy są pomijane.

Adnotacja RequiresDeviceMode

Adnotacji RequiresDeviceMode można użyć wielokrotnie, aby wskazać test, który będzie uruchamiany tylko wtedy, gdy urządzenie obsługuje wszystkie wartości 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()))
    }
}

Adnotacja RequiresDisplay

Adnotacja RequiresDisplay umożliwia określenie szerokości i wysokości ekranu urządzenia za pomocą klas rozmiarów, które definiują zasobniki wymiarów zgodnie z oficjalnymi klasami rozmiarów okien.

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

Zmienianie rozmiaru ekranów

Użyj metody setDisplaySize(), aby zmienić rozmiar ekranu w czasie działania. Użyj tej metody w połączeniu z klasą DisplaySizeRule , która zapewnia, że wszelkie zmiany wprowadzone podczas testów zostaną cofnięte przed następnym testem.

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

Gdy zmieniasz rozmiar ekranu za pomocą setDisplaySize(), nie wpływasz na gęstość urządzenia, więc jeśli wymiar nie mieści się na urządzeniu docelowym, test kończy się niepowodzeniem z powodu UnsupportedDeviceOperationException. Aby zapobiec uruchamianiu testów w tym przypadku, użyj adnotacji RequiresDisplay, aby je odfiltrować:

@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

Klasa StateRestorationTester służy do testowania przywracania stanu komponentów kompozycyjnych bez ponownego tworzenia aktywności. Dzięki temu testy są szybsze i bardziej niezawodne, ponieważ ponowne tworzenie aktywności to złożony proces z wieloma mechanizmami synchronizacji:

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

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

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

Biblioteka testowania okien

Biblioteka testowania okien zawiera narzędzia, które pomagają pisać testy korzystające z funkcji związanych z zarządzaniem oknami lub je weryfikujące, takich jak osadzanie aktywności czy funkcje urządzeń składanych. Artefakt jest dostępny w repozytorium Maven Google.

Możesz na przykład użyć funkcji FoldingFeature(), aby wygenerować niestandardową funkcję FoldingFeature, której możesz używać w podglądach Compose. W Javie, użyj funkcji createFoldingFeature().

W podglądzie Compose możesz zaimplementować FoldingFeature w ten sposób:

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

Możesz też emulować funkcje ekranu w testach interfejsu za pomocą funkcji TestWindowLayoutInfo(). Ten przykład symuluje FoldingFeature z HALF_OPENED pionowym zawiasem na środku ekranu, a następnie sprawdza, czy układ jest zgodny z oczekiwanym:

Utwórz

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

Wyświetlenia

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

Więcej przykładów znajdziesz w projekcie WindowManager.

Dodatkowe materiały

Dokumentacja

Przykłady

Codelabs