В Android есть множество инструментов и API, которые помогут вам создать тесты для разных размеров экрана и окна.
DeviceConfigurationOverride
Composable-функция DeviceConfigurationOverride позволяет переопределять атрибуты конфигурации, чтобы тестировать макеты Compose на экранах и в окнах разных размеров. Переопределение ForcedSize позволяет вписать любой макет в доступное пространство, чтобы вы могли протестировать интерфейс на экране любого размера. Например, вы можете использовать небольшой телефонный форм-фактор для запуска всех тестов пользовательского интерфейса, включая тесты пользовательского интерфейса для больших телефонов, складных устройств и планшетов.
DeviceConfigurationOverride(
DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
) {
MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
}
Кроме того, вы можете использовать эту composable-функцию, чтобы задать масштаб шрифта, темы и другие свойства, которые вы хотите протестировать на разных размерах окон.
Robolectric
Используйте Robolectric, чтобы запускать тесты Compose или интерфейса на основе представлений на JVM локально – устройства или эмуляторы не требуются. Вы можете настроить Robolectric так, чтобы он использовал определенные размеры экрана и другие полезные свойства.
В следующем примере из Now in Android Robolectric настроен на эмуляцию экрана размером 1000 x 1000 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 { ... }
Вы также можете задать квалификаторы в теле теста, как показано в этом фрагменте из примера Now in 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 с непрерывной интеграцией (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 Test Lab
Используйте Firebase Test Lab или аналогичный сервис, чтобы тестировать приложение на реальных устройствах, к которым у вас нет доступа, например на складных телефонах или планшетах разных размеров. 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
Используйте 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 Emulator 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
Аннотация 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 используется для тестирования восстановления состояния composable-функций без повторного создания Activity. Это делает тесты быстрее и надежнее, поскольку воссоздание активности – сложный процесс с несколькими механизмами синхронизации.
@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
Библиотека Window Testing содержит утилиты, которые помогают писать тесты, основанные на функциях, связанных с управлением окнами, или проверяющие их. К таким функциям относятся, например, встраивание действий и функции для складных устройств. Артефакт доступен в репозитории 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.
Дополнительные ресурсы
Документация
- Рекомендации по обеспечению качества приложений для больших экранов
- Как тестировать приложения на Android
- Тестирование макета Compose
Образцы
- Пример WindowManager
- Примеры устройств Espresso
- Now In Android
- Использует тестирование скриншотов для проверки разных размеров экрана.
Практические работы