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

Android udostępnia różne narzędzia i interfejsy API, które pomagają tworzyć testy dla różnych rozmiarów ekranu i okien.

DeviceConfigurationOverride

Komponent DeviceConfigurationOverride umożliwia zastępowanie atrybutów konfiguracji w celu testowania wielu rozmiarów ekranu i okna w układach Compose. Zastąpienie ForcedSize pasuje do każdego układu w dostępnej przestrzeni, dzięki czemu możesz przeprowadzać dowolne testy interfejsu na ekranach o dowolnym rozmiarze. Możesz na przykład używać małego telefonu do przeprowadzania wszystkich testów interfejsu, w tym testów 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 dopasowywania układu tabletu do mniejszego urządzenia, jak w przypadku \*Now in Android*.

Za pomocą tego komponentu możesz też ustawić skalę czcionki, motywy i inne właściwości, które chcesz przetestować w różnych rozmiarach okna.

Robolectric

Używaj Robolectric do przeprowadzania testów interfejsu opartych na Compose lub widokach 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, a także innych przydatnych właściwości.

W tym przykładzieNow in Android 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 { ... }

Kwalifikatory możesz też ustawić w treści testu, jak w tym fragmencie 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 za pomocą nowej konfiguracji, ale nie wywołuje żadnych działań w przypadku aktywnych działań ani innych komponentów.

Więcej informacji znajdziesz w dokumentacji Robolectric Device Configuration (Konfiguracja urządzenia).

Urządzenia zarządzane przez Gradle

Wtyczka Androida do Gradle urządzenia zarządzane przez Gradle (GMD) umożliwia określanie specyfikacji emulatorów i prawdziwych urządzeń, na których są uruchamiane instrumentowane testy. Utwórz specyfikacje urządzeń o różnych rozmiarach ekranu, aby wdrożyć strategię testowania, w której określone testy muszą być przeprowadzane na określonych rozmiarach ekranu. Korzystając z GMD w trybie ciągłej integracji (CI), możesz mieć pewność, że w razie potrzeby zostaną uruchomione odpowiednie testy, a także udostępniać i uruchamiać emulatory oraz upraszczać 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żywaj Laboratorium Firebase (FTL) lub podobnej usługi farmy urządzeń, aby przeprowadzać testy na konkretnych urządzeniach, do których możesz nie mieć dostępu, np. na składanych telefonach lub tabletach o różnych rozmiarach. Laboratorium Firebase to płatna usługa z poziomem bezpłatnym. FTL umożliwia też przeprowadzanie testów na emulatorach. Te usługi zwiększają niezawodność i szybkość testów z instrumentacją, ponieważ mogą z wyprzedzeniem udostępniać urządzenia i emulatory.

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

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

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

Możesz dodać adnotacje do niektórych testów, 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

Używaj ich w różnych testach:

class MyTestClass {

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

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

}

Podczas przeprowadzania 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 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.

Urządzenie do espresso

Używaj Espresso Device do wykonywania działań na emulatorach w testach z użyciem dowolnego rodzaju testów instrumentowanych, w tym testów Espresso, Compose lub UI Automator. Mogą one obejmować ustawianie rozmiaru ekranu lub przełączanie stanów składania i położenia. 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 fazie 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,

Filtrowanie testów

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

Adnotacja RequiresDeviceMode

Adnotacji RequiresDeviceMode można użyć kilka razy, aby wskazać test, który będzie przeprowadzany 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()))
    }
}

Adnotacja RequiresDisplay

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

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

Zmiana 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ą 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 ekranu za pomocą ikony setDisplaySize() nie wpływa na gęstość urządzenia, więc jeśli wymiar nie mieści się na urządzeniu docelowym, test kończy się niepowodzeniem i wyświetla się ikona UnsupportedDeviceOperationException. Aby w takim przypadku zapobiec uruchamianiu testów, 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ż 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 pomagają pisać testy korzystające z funkcji związanych z zarządzaniem oknami lub weryfikujące te funkcje, takie 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ć niestandardowy FoldingFeature, którego możesz używać w podglądach kompozycji. W języku Java 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)))
        )
 }

W testach interfejsu możesz też emulować funkcje wyświetlania za pomocą funkcji TestWindowLayoutInfo(). Poniższy przykład symuluje FoldingFeatureHALF_OPENED pionowym zawiasem na ś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

Codelabs