Библиотеки и инструменты для тестирования экранов разных размеров

Android предоставляет множество инструментов и API, которые помогут вам создавать тесты для разных размеров экранов и окон.

Переопределение конфигурации устройства

Компонуемый DeviceConfigurationOverride позволяет переопределять атрибуты конфигурации для тестирования нескольких размеров экрана и окна в макетах Compose. Переопределение ForcedSize подходит для любого макета в доступном пространстве, что позволяет вам запускать любые тесты пользовательского интерфейса на экране любого размера. Например, вы можете использовать небольшой форм-фактор телефона для запуска всех тестов пользовательского интерфейса, включая тесты пользовательского интерфейса для больших телефонов, складных устройств и планшетов.

   DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
Рисунок 1. Использование DeviceConfigurationOverride для размещения макета планшета внутри устройства меньшего форм-фактора, как в \*Теперь в Android*.

Кроме того, вы можете использовать этот компонуемый объект для установки масштаба шрифта, тем и других свойств, которые вы, возможно, захотите протестировать на окнах разных размеров.

Робоэлектрик

Используйте Robolectric для локального запуска Compose или UI-тестов на основе представления на JVM — не требуются никакие устройства или эмуляторы. Вы можете настроить Robolectric для использования определенных размеров экрана, среди прочих полезных свойств.

В следующем примере из Now in Android Robolectric настроен на эмуляцию экрана размером 1000x1000 точек на дюйм с разрешением 480 точек на дюйм:

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

Вы также можете задать квалификаторы из тела теста, как это сделано в этом фрагменте из примера Now в Android :

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

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

Обратите внимание, что RuntimeEnvironment.setQualifiers() обновляет системные и прикладные ресурсы с использованием новой конфигурации, но не запускает никаких действий в активных действиях или других компонентах.

Более подробную информацию можно найти в документации по конфигурации Robolectric Device .

Устройства, управляемые Gradle

Плагин Gradle-managed devices (GMD) Android Gradle позволяет вам определять спецификации эмуляторов и реальных устройств, на которых запускаются ваши инструментированные тесты. Создавайте спецификации для устройств с разными размерами экрана, чтобы реализовать стратегию тестирования, при которой определенные тесты должны запускаться на экранах определенных размеров. Используя GMD с непрерывной интеграцией (CI), вы можете быть уверены, что соответствующие тесты запускаются при необходимости, предоставляя и запуская эмуляторы и упрощая настройку 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"
                }
            }
        }
    }
}

Несколько примеров GMD можно найти в проекте testing-samples .

Тестовая лаборатория Firebase

Используйте Firebase Test Lab (FTL) или подобную службу фермы устройств для запуска тестов на конкретных реальных устройствах, к которым у вас может не быть доступа, например, складных устройствах или планшетах разных размеров. Firebase Test Lab — это платная услуга с бесплатным уровнем . FTL также поддерживает запуск тестов на эмуляторах. Эти службы повышают надежность и скорость инструментального тестирования, поскольку они могут заранее предоставлять устройства и эмуляторы.

Информацию об использовании FTL с GMD см. в разделе Масштабирование тестов с помощью устройств, управляемых Gradle .

Тестовая фильтрация с помощью тестового раннера

Оптимальная стратегия тестирования не должна проверять одно и то же дважды, поэтому большинство ваших тестов пользовательского интерфейса не нужно запускать на нескольких устройствах. Обычно вы фильтруете свои тесты пользовательского интерфейса, запуская все или большинство из них на форм-факторе телефона и только подмножество на устройствах с разными размерами экрана.

Вы можете пометить определенные тесты так, чтобы они запускались только на определенных устройствах, а затем передать аргумент в AndroidJUnitRunner с помощью команды, которая запускает тесты.

Например, вы можете создавать различные аннотации:

annotation class TestExpandedWidth
annotation class TestCompactWidth

И используйте их в различных тестах:

class MyTestClass {

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

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

}

Затем вы можете использовать свойство android.testInstrumentationRunnerArguments.annotation при запуске тестов для фильтрации определенных. Например, если вы используете устройства, управляемые Gradle:

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

Если вы не используете GMD и управляете эмуляторами в CI, сначала убедитесь, что нужный эмулятор или устройство готовы и подключены, а затем передайте параметр одной из команд Gradle для запуска инструментированных тестов:

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

Обратите внимание, что Espresso Device (см. следующий раздел) также может фильтровать тесты, используя свойства устройства.

Эспрессо-устройство

Используйте Espresso Device для выполнения действий на эмуляторах в тестах с использованием любого типа инструментальных тестов, включая тесты Espresso, Compose или UI Automator. Эти действия могут включать установку размера экрана или переключение складных состояний или поз. Например, вы можете управлять складным эмулятором и устанавливать его в настольный режим. Espresso Device также содержит правила и аннотации JUnit для требования определенных функций:

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

Обратите внимание, что Espresso Device все еще находится на стадии альфа-тестирования и имеет следующие требования:

  • Плагин Android Gradle 8.3 или выше
  • Эмулятор Android 33.1.10 или выше
  • Виртуальное устройство Android, работающее под управлением API уровня 24 или выше

Тесты фильтров

Espresso Device может считывать свойства подключенных устройств, чтобы вы могли фильтровать тесты с помощью аннотаций . Если аннотированные требования не выполняются, тесты пропускаются.

Требуется аннотация DeviceMode

Аннотацию RequiresDeviceMode можно использовать несколько раз для указания теста, который будет запущен только в том случае, если на устройстве поддерживаются все значения 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()))
    }
}

ТребуетсяОтображение аннотации

Аннотация RequiresDisplay позволяет указать ширину и высоту экрана устройства с помощью классов размеров , которые определяют размерные области в соответствии с официальными классами размеров окна .

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

Изменение размера дисплеев

Используйте метод setDisplaySize() для изменения размеров экрана во время выполнения. Используйте метод в сочетании с классом DisplaySizeRule , который гарантирует, что любые изменения, сделанные во время тестов, будут отменены перед следующим тестом.

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

При изменении размера дисплея с помощью setDisplaySize() вы не влияете на плотность устройства, поэтому, если размер не помещается в целевое устройство, тест завершается неудачей с исключением UnsupportedDeviceOperationException . Чтобы предотвратить запуск тестов в этом случае, используйте аннотацию 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.
    }
}

StateRestorationTester

Класс StateRestorationTester используется для тестирования восстановления состояния для составных компонентов без повторного создания активностей. Это делает тесты более быстрыми и надежными, поскольку воссоздание активности — сложный процесс с несколькими механизмами синхронизации:

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

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

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

Библиотека тестирования окон

Библиотека Window Testing содержит утилиты, которые помогут вам писать тесты, которые полагаются на или проверяют функции, связанные с управлением окнами, такие как встраивание активности или сворачиваемые функции. Артефакт доступен через репозиторий Maven от Google .

Например, вы можете использовать функцию FoldingFeature() для генерации пользовательского FoldingFeature , который вы можете использовать в предпросмотрах Compose. В Java используйте функцию createFoldingFeature() .

В предварительном просмотре Compose вы можете реализовать FoldingFeature следующим образом:

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

Также вы можете эмулировать функции отображения в тестах пользовательского интерфейса с помощью функции TestWindowLayoutInfo() . Следующий пример имитирует FoldingFeature с HALF_OPENED вертикальным шарниром в центре экрана, а затем проверяет, соответствует ли макет ожидаемому:

Сочинять

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

Просмотры

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

Больше примеров вы можете найти в проекте WindowManager .

Дополнительные ресурсы

Документация

Образцы

Кодовые лаборатории