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

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

Zastąpienie konfiguracji urządzenia

Funkcja kompozycyjna DeviceConfigurationOverride umożliwia zastąpienie atrybutów konfiguracji w celu przetestowania różnych rozmiarów ekranu i okien w układach tworzenia wiadomości. Zastąpienie ForcedSize pasuje do dowolnego układu w dostępnej przestrzeni, dzięki czemu możesz przeprowadzić dowolny test interfejsu na ekranie o dowolnym rozmiarze. Za pomocą małego telefonu możesz na przykład przeprowadzić wszystkie testy UI, w tym testy UI dużych telefonów, urządzeń składanych i tabletów.

   DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
Rysunek 1. Użycie elementu DeviceConfigurationOverride w celu dopasowania układu tabletu do mniejszych urządzeń, na przykład \*Now w Androidzie*.

Za pomocą tej funkcji możesz też ustawiać skalę czcionek, motywy i inne właściwości, które zechcesz przetestować w oknach o różnych rozmiarach.

Robolectric

Za pomocą Robolectric możesz lokalnie uruchamiać testy interfejsu użytkownika na maszynie wirtualnej JVM lub na podstawie widoków. Nie musisz używać żadnych urządzeń ani emulatorów. Możesz skonfigurować Robolectric, aby m.in. korzystał z określonych rozmiarów ekranu.

W tym przykładzie z wersji na Androida rozwiązanie Robolectric zostało skonfigurowane tak, aby emulować rozmiar ekranu 1000 x 1000 dp z 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 testowej tak, jak to widać 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 przypadku aktywnych działań ani innych komponentów.

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

Urządzenia zarządzane przez Gradle

Wtyczka Androida do obsługi Gradle do obsługi Gradle (GMD) pozwala zdefiniować specyfikacje emulatorów i rzeczywistych urządzeń, na których przeprowadzane są testy instrumentowane. 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. Korzystając z GMD w połączeniu z ciągłą integracją (CI), możesz zadbać o to, aby w razie potrzeby uruchomić odpowiednie testy, udostępnić 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"
                }
            }
        }
    }
}

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

Laboratorium Firebase

Skorzystaj z Laboratorium Firebase (FTL) lub podobnej usługi farmy urządzeń, aby przeprowadzić testy na konkretnych urządzeniach, do których możesz nie mieć dostępu, np. na urządzeniach składanych lub tabletach różnych rozmiarów. Laboratorium Firebase to płatna usługa na poziomie bezpłatnym. FTL umożliwia też przeprowadzanie testów na emulatorach. Usługi te zwiększają niezawodność i szybkość testów instrumentalnych, ponieważ umożliwiają z wyprzedzeniem udostępnianie urządzeń i emulatorów.

Informacje na temat korzystania z FTL w GMD znajdziesz w artykule Skalowanie testów na urządzeniach zarządzanych przez Gradle.

Testowanie filtrowania za pomocą mechanizmu uruchamiania testów

Optymalna strategia testowa nie powinna przeprowadzać weryfikacji tego samego elementu dwukrotnie, dzięki czemu większość testów interfejsu nie musi być przeprowadzana na różnych urządzeniach. Zwykle filtruje się testy interfejsu, uruchamiając wszystkie lub większość z nich na telefonie i tylko niektóre testy na urządzeniach o różnych rozmiarach ekranu.

Możesz dodawać adnotacje do testów, które mają być uruchamiane tylko na określonych urządzeniach, a następnie przekazywać 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

Użyj 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 podczas uruchamiania testów, aby odfiltrowywać konkretne z nich. Jeśli na przykład korzystasz z 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 właściwy emulator lub urządzenie jest gotowe i podłączone, a potem przekaż parametr do jednego z poleceń Gradle, aby przeprowadzić testy zinstruowane:

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

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

Ekspres do kawy

Za pomocą narzędzia Espresso Device możesz wykonywać działania na emulatorach w testach, korzystając z dowolnego typu testów instrumentowanych, takich jak Espresso, Compose czy UI Automator. Działania te mogą obejmować ustawianie rozmiaru ekranu lub przełączanie stanów lub stanów urządzenia składanego. Możesz na przykład sterować składanym emulatorem i ustawić go w trybie stołu. Urządzenie do espresso zawiera również reguły JUnit i adnotacje, 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 urządzenie Espresso jest wciąż w wersji alfa i spełnia te wymagania:

  • Wtyczka Androida do obsługi Gradle w wersji 8.3 lub nowszej
  • Emulator Androida w wersji 33.1.10 lub nowszej
  • Urządzenie wirtualne z Androidem, na którym działa interfejs 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ć filtrowanie testów za pomocą adnotacji. Jeśli te wymagania nie zostaną spełnione, testy zostaną pominięte.

Wymaga adnotacji trybu urządzenia

Adnotacji RequiresDeviceMode można używać wiele razy, 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 displayowej

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

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

Zmień rozmiar wyświetlaczy

Aby zmienić rozmiar ekranu w czasie działania, użyj metody setDisplaySize(). Używaj tej metody w połączeniu z klasą DisplaySizeRule, dzięki czemu wszystkie 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 wpływa na gęstość ekranu urządzenia, więc jeśli dany wymiar nie mieści się na urządzeniu docelowym, test zakończy się niepowodzeniem z parametrem UnsupportedDeviceOperationException. Aby w takim przypadku uniemożliwić przeprowadzanie 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.
    }
}

Tester przywrócenia stanu

Klasa StateRestorationTester służy do testowania przywracania stanu komponentów kompozycyjnych bez odtwarzania 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 pomogą Ci pisać testy, które wymagają funkcji 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 Maven.

Możesz na przykład użyć funkcji FoldingFeature(), aby wygenerować niestandardową wartość FoldingFeature, której można użyć 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)))
        )
 }

W testach interfejsu możesz też emulować funkcje wyświetlania za pomocą funkcji TestWindowLayoutInfo(). W tym przykładzie symulujemy element 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świetleń

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