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

Android udostępnia wiele narzędzi i interfejsów API, które pomagają tworzyć testy na ekrany i okna o różnych rozmiarach.

DeviceConfigurationOverride

Element kompozycyjny DeviceConfigurationOverride pozwala zastąpić atrybuty konfiguracji w celu przetestowania różnych rozmiarów ekranu i okien w układach tworzenia wiadomości. Zastępowanie elementu ForcedSize pasuje do każdego układu w dostępnym miejscu, dzięki czemu możesz przeprowadzić dowolny test interfejsu na ekranie o dowolnym rozmiarze. Możesz na przykład przeprowadzić wszystkie testy UI, w tym testy UI 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. za pomocą parametru DeviceConfigurationOverride, który pozwala dopasować układ tabletu do mniejszego urządzenia, na przykład \*Now in Android*

Dodatkowo za pomocą tego elementu kompozycyjnego możesz 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 przeprowadzać testy interfejsu Compose lub oparte na wyświetleniu w JVM lokalnie – nie są wymagane urządzenia ani emulatory. Możesz skonfigurować program Roobolectric, aby używać określonych rozmiarów ekranu i innych przydatnych właściwości.

W tym przykładzie z Now in Android Robolectric skonfigurowano do emulacji ekranu o rozdzielczości 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 z treści testu, tak 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 przy użyciu nowej konfiguracji, ale nie wywołuje żadnych działań na aktywnych działaniach ani innych komponentach.

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

Urządzenia zarządzane przez Gradle

Wtyczka Androida do obsługi urządzeń zarządzanych przez Gradle (GMD) pozwala określić specyfikacje emulatorów i prawdziwych urządzeń, na których uruchamiane są instrumenty testów. Utwórz specyfikacje dla urządzeń z ekranami o różnych rozmiarach, aby wdrożyć strategię testowania, w ramach której określone testy muszą być przeprowadzane na ekranach o określonych rozmiarach. Używając GMD z Ciągłą integracją (CI), możesz mieć pewność, że w razie potrzeby uruchamiają się odpowiednie testy, udostępniać i uruchamiać emulatory oraz 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

Skorzystaj z Laboratorium Firebase lub podobnej usługi farmy urządzeń, by przeprowadzić testy na konkretnych rzeczywistych urządzeniach, do których możesz nie mieć dostępu, takich jak urządzenia składane lub tablety o różnej wielkości. Laboratorium Firebase to płatna usługa na poziomie bezpłatnym. FTL obsługuje też testy w emulatorach. Usługi te zwiększają niezawodność i szybkość testów z instrumentacją, ponieważ mogą udostępnić urządzenia i emulatory z wyprzedzeniem.

Informacje o korzystaniu z FTL w GMD znajdziesz w artykule Skalowanie testów przy użyciu urządzeń zarządzanych przez Gradle.

Filtrowanie testów za pomocą komponentu uruchamiającego test

Optymalna strategia testowa nie powinna weryfikować tego samego elementu dwa razy, więc większość testów interfejsu nie musi być przeprowadzana na wielu urządzeniach. Zwykle filtrujesz testy interfejsu, wykonując wszystkie lub większość z nich na telefonie i tylko na niektórych urządzeniach z ekranami o różnych rozmiarach.

Możesz dodać adnotacje do wybranych testów, które mają zostać przeprowadzone tylko na określonych urządzeniach, a następnie przekazać argument do AndroidJUnitRunner, używając polecenia, które uruchamia testy.

Możesz na przykład tworzyć 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() {
        ...
    }

}

Następnie możesz używać właściwości android.testInstrumentationRunnerArguments.annotation do filtrowania konkretnych testów podczas wykonywania testów. Na przykład jeśli 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 przeprowadzić testy instrumentowane:

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

Pamiętaj, że urządzenie do espresso (patrz następna sekcja) może też filtrować testy na podstawie właściwości urządzenia.

Ekspres do kawy

Urządzenie Espresso pozwala wykonywać działania na emulatorach w testach z wykorzystaniem dowolnego typu testów z instrumentami, np. Espresso, Compose czy Automator interfejsu. Czynności te mogą obejmować ustawianie rozmiaru ekranu oraz przełączanie trybów składanych i pozycji. Możesz na przykład sterować składanym emulatorem i ustawić go w trybie stołu. 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()))
    }
}

Uwaga: urządzenie Espresso jest nadal w wersji alfa i ma następujące wymagania:

  • Wtyczka Androida do obsługi Gradle w wersji 8.3 lub nowszej
  • Emulator Androida w wersji 33.1.10 lub nowszej
  • Wirtualne urządzenie z Androidem z interfejsem API na poziomie 24 lub wyższym

Filtruj testy

Urządzenie do espresso może odczytywać właściwości połączonych urządzeń, aby umożliwić Ci filtrowanie testów za pomocą adnotacji. Jeśli wymagania z adnotacjami nie zostaną spełnione, testy są pomijane.

Adnotacja RequiresDeviceMode

Adnotacji RequiresDeviceMode można używać wielokrotnie, aby wskazać test, który zostanie uruchomiony tylko wtedy, gdy urządzenie będzie obsługiwać 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()))
    }
}

Wymaga adnotacji Display

Adnotacja RequiresDisplay pozwala określić szerokość i wysokość 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() {
        ...
    }
}

Zmiana rozmiaru wyświetlaczy

Aby zmieniać rozmiar ekranu w czasie działania, użyj metody setDisplaySize(). Używaj jej w połączeniu z klasą DisplaySizeRule, dzięki czemu wszelkie zmiany wprowadzone podczas testów zostaną cofnięte przed kolejnym 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.
    }
}

Zmiana rozmiaru wyświetlacza za pomocą funkcji setDisplaySize() nie ma wpływu na gęstość urządzenia. Jeśli więc wymiar nie pasuje do urządzenia docelowego, test kończy się wynikiem UnsupportedDeviceOperationException. Aby w takim przypadku uniemożliwić uruchomienie testów, odfiltruj je za pomocą adnotacji 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.
    }
}

Stanowy tester rewitalizacji

Klasa StateRestorationTester służy do testowania przywracania stanu komponentów kompozycyjnych bez ponownego tworzenia działań. Dzięki temu testy są szybsze i bardziej niezawodne, ponieważ odtwarzanie 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, które bazują na funkcjach związanych z zarządzaniem oknami lub je sprawdzają, takie jak umieszczanie aktywności czy funkcje składane. Artefakt jest dostępny w repozytorium Google Manufacturer Center.

Możesz na przykład użyć funkcji FoldingFeature(), aby wygenerować niestandardowy FoldingFeature, którego możesz używać w podglądach tworzenia wiadomości. W Javie użyj funkcji createFoldingFeature().

W podglądzie tworzenia wiadomości możesz zaimplementować FoldingFeature w taki 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 wyświetlania w testach interfejsu za pomocą funkcji TestWindowLayoutInfo(). Ten przykład pokazuje symulację elementu FoldingFeature z pionowym zawiasem HALF_OPENED poś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 WindowManagera.

Dodatkowe materiały

Dokumentacja

Próbki

Ćwiczenia z programowania