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

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 или View на JVM — никаких устройств или эмуляторов не требуется. Помимо других полезных свойств, вы можете настроить Robolectric на использование определенных размеров экрана.

В следующем примере из раздела «Сейчас в 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 .

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

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

Образцы

Кодлабы