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

Android udostępnia różne narzędzia i interfejsy API, które ułatwiają tworzenie testów na różne rozmiary ekranu i okna.

DeviceConfigurationOverride

Komponent DeviceConfigurationOverride umożliwia zastąpienie atrybutów konfiguracji, aby testować różne rozmiary ekranu i okna w układach Compose. Zastąpienie ForcedSize pasuje do dowolnego układu w dostępnym miejscu, co pozwala przeprowadzić dowolny test interfejsu użytkownika na dowolnym rozmiarze ekranu. Możesz na przykład używać małego telefonu do przeprowadzania wszystkich testów interfejsu użytkownika, w tym testów interfejsu użytkownika na dużych telefonach, składanych urządzeniach 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 urządzenia o mniejszym formacie, jak w \*Now in Android*.

Możesz też użyć tej komponenty do ustawienia skali czcionki, motywów i innych właściwości, które chcesz przetestować w różnych rozmiarach okna.

Robolectric

Użyj Robolectric do przeprowadzania testów interfejsu użytkownika opartych na Compose lub widoku na maszynie JVM lokalnie – 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ładzieNowości w Androidzie Robolectric jest skonfigurowany tak, aby emulować rozmiar ekranu 1000 x 1000 dp przy 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 ciele testu, tak jak w tym fragmencie kodu z Nowości w Androidzie:

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 zgodnie z nową konfiguracją, ale nie powoduje żadnych działań w przypadku aktywnych aktywności ani innych komponentów.

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

Urządzenia zarządzane przez Gradle

W pluginie Gradle dla Androida urządzenia zarządzane przez Gradle (GMD) możesz definiować specyfikacje emulatorów i prawdziwych urządzeń, na których będą działać testy z instrumentacją. Utwórz specyfikacje urządzeń o różnych rozmiarach ekranu, aby wdrożyć strategię testowania, w ramach której określone testy muszą być przeprowadzane na określonych rozmiarach ekranu. Korzystając z GMD w ramach ciągłej integracji (CI), możesz mieć pewność, że odpowiednie testy są wykonywane w odpowiednich momentach. Możesz też łatwiej konfigurować CI, a także udostępniać i uruchamiać emulatory.

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

Więcej przykładów metody GMD znajdziesz w projekcie testing-samples.

Laboratorium Firebase

Użyj Laboratorium Testowego Firebase (FTL) lub podobnej usługi farmy urządzeń, aby przeprowadzić testy na konkretnych prawdziwych urządzeniach, do których możesz nie mieć dostępu, takich jak składane urządzenia czy tablety o różnych rozmiarach. Laboratorium Firebase to płatna usługa z poziomem bezpłatnym. FTL obsługuje też uruchamianie testów na emulatorach. Te usługi zwiększają niezawodność i szybkość testowania z wykorzystaniem instrumentacji, ponieważ mogą z wyprzedzeniem przygotowywać urządzenia i emulatory.

Informacje o używaniu FTL z GDM znajdziesz w artykule Testowanie na wielu urządzeniach z użyciem Gradle-managed devices.

Testowanie filtrowania za pomocą narzędzia do uruchamiania testów

Optymalna strategia testowania nie powinna sprawdzać tego samego dwukrotnie, więc większość testów interfejsu użytkownika nie musi być uruchamiana na wielu urządzeniach. Testy UI zwykle filtrujesz, uruchamiając wszystkie lub większość z nich na telefonie, a tylko podzbiór na urządzeniach z różnymi rozmiarami ekranu.

Możesz oznaczyć niektóre testy jako przeznaczone do uruchamiania tylko na określonych urządzeniach, a potem przekazać argument AndroidJUnitRunner za pomocą polecenia, które uruchamia testy.

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

annotation class TestExpandedWidth
annotation class TestCompactWidth

Używaj ich w różnych testach:

class MyTestClass {

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

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

}

Następnie podczas uruchamiania testów możesz użyć właściwości android.testInstrumentationRunnerArguments.annotation, aby filtrować konkretne 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 podłączone, a potem prześlij parametr do jednego z tych poleceń Gradle, aby uruchomić testy z użyciem instrumentacji:

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

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

Urządzenie do espresso

Używaj Espresso Device do wykonywania działań na emulatorach w testach z wykorzystaniem dowolnego typu testów z użyciem instrumentów, w tym testów Espresso, Compose lub UI Automator. Mogą to być na przykład ustawienia rozmiaru ekranu lub przełączanie stanów składania lub pozycji. Możesz na przykład sterować składanym emulatorem 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 fazie alfa i wymaga:

  • Wtyczka Androida do obsługi Gradle w wersji 8.3 lub nowszej
  • emulator Androida 33.1.10 lub nowszy.
  • wirtualne urządzenie z Androidem na poziomie interfejsu API 24 lub nowszym,

Testy filtra

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 opisane w komentarzach nie zostaną spełnione, testy zostaną pominięte.

Adnotacja RequiresDeviceMode

Adnotację RequiresDeviceMode można użyć wielokrotnie, aby wskazać test, który będzie wykonywany tylko wtedy, gdy wszystkie wartości DeviceMode są obsługiwane na urządzeniu.

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 wyświetlania

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

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

Zmienianie rozmiaru wyświetlaczy

Aby zmienić rozmiar ekranu w czasie działania, użyj metody setDisplaySize(). Używaj tej metody w połączeniu z klasą DisplaySizeRule, która zapewnia, że wszelkie zmiany wprowadzone podczas testów zostaną wycofane 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 wyświetlacza za pomocą setDisplaySize(), nie wpływasz na gęstość urządzenia, więc jeśli wymiar nie pasuje do urządzenia docelowego, test się nie powiedzie i wyświetli komunikat UnsupportedDeviceOperationException. Aby zapobiec uruchamianiu testów w takim 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 składanych 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 testów okien

Biblioteka Window Testing zawiera narzędzia, które ułatwiają tworzenie testów wykorzystujących funkcje związane z zarządzaniem oknami lub je weryfikujących, np. osadzanie aktywności czy funkcje składania. Element ten jest dostępny w Repozytorium Maven Google.

Możesz na przykład użyć funkcji FoldingFeature(), aby wygenerować niestandardowy FoldingFeature, którego możesz używać w podglądach w sekcji Komponowanie. W języku Java użyj funkcji createFoldingFeature().

W podglądzie tworzenia 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)))
        )
 }

W testach interfejsu możesz też emulować funkcje wyświetlania za pomocą funkcji TestWindowLayoutInfo(). Ten przykład symuluje FoldingFeatureHALF_OPENEDpionowym zawiasem w środku ekranu, a następnie sprawdza, czy układ jest zgodny z oczekiwanym:

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

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

Próbki

Ćwiczenia z programowania