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

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 для размещения макета планшета внутри устройства меньшего форм-фактора, как в \*Now в Android*.

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

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

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

В следующем примере из Now in Android Robolectric настроен на эмуляцию экрана размером 1000x1000 dp с разрешением 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 { ... }

Вы также можете установить квалификаторы из тела теста, как это сделано в этом фрагменте из примера «Сейчас в Android» :

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

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

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

Дополнительную информацию можно прочитать в документации по настройке устройства Robolectric.

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

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

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

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

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

Тестовая фильтрация с помощью средства запуска тестов

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

Вы можете аннотировать определенные тесты для запуска только на определенных устройствах, а затем передать аргумент 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 может считывать свойства подключенных устройств, чтобы вы могли фильтровать тесты с помощью аннотаций . Если аннотированные требования не выполняются, тесты пропускаются.

Аннотация RequiresDeviceMode

Аннотацию 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.
    }
}

StateRestoreTester

Класс 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()
}

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

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

Например, вы можете использовать функцию 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 .

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

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

Образцы

Кодлабы