Фундамент тестирования в Compose заложен в самой библиотеке `androidx.compose.ui:ui-test`. Она предоставляет два основных типа тестов: **тесты семантики** и **тесты синхронизации**. Семантика — это ключевое понятие. Она описывает смысл UI-элементов для accessibility-сервисов и, что важно для нас, для тестов. По сути, это абстрактное дерево, которое Compose строит поверх композиции, где каждый узел содержит свойства вроде `text`, `contentDescription`, `isToggleable` и т.д. Тесты взаимодействуют именно с этим семантическим деревом, что делает их устойчивыми к косметическим изменениям в верстке.
Начнем с настройки. В модуле `build.gradle.kts` вам понадобятся зависимости:
`androidTestImplementation(androidx.compose.ui:ui-test-junit4)`
`androidTestImplementation(androidx.compose.ui:ui-test-manifest)`
`debugImplementation(androidx.compose.ui:ui-test-manifest)` // Для создания тестового Activity
Также необходимо использовать правило `createComposeRule()` или `createAndroidComposeRule()`.
Первый и самый распространенный вид — **тестирование состояния (State Testing)**. Поскольку UI в Compose — функция состояния, мы можем тестировать его, просто меняя state и проверяя результат. Допустим, у нас есть компонент `Greeting`, который принимает имя. Мы можем написать тест так:
`@Test
fun Greeting_should_display_correct_text() {
val composeTestRule = createComposeRule()
composeTestRule.setContent {
Greeting(name = "Android")
}
composeTestRule.onNodeWithText("Hello Android!").assertIsDisplayed()
}`
Здесь `onNodeWithText` — семантический матчер, который ищет узел с заданным текстом. `assertIsDisplayed()` — утверждение (assertion). Это базовый паттерн.
Второй критически важный вид — **тестирование взаимодействий (Interaction Testing)**. Compose Test API позволяет симулировать действия пользователя: клики, ввод текста, свайпы. Протестируем простой переключатель:
`@Test
fun MyToggle_should_change_state_on_click() {
composeTestRule.setContent { MyToggle() }
// Находим переключатель по его роли в семантическом дереве
val toggleNode = composeTestRule.onNodeWithContentDescription("My Toggle Switch")
// Проверяем начальное состояние (выключен)
toggleNode.assertIsOff()
// Совершаем клик
toggleNode.performClick()
// Проверяем, что состояние изменилось на "включен"
toggleNode.assertIsOn()
}`
Для тестирования ввода текста в `TextField` используется `performTextInput()` или `performTextReplacement()`.
Третий уровень — **тестирование на разных конфигурациях (UI-ресурсов)**. Это мощь Compose в действии. С помощью `createComposeRule()` и аннотаций `@Test` вы можете легко тестировать отображение при разных размерах шрифта, режимах темы (светлая/темная) или локалях. Инструмент `ComposeTestRule` позволяет динамически менять конфигурацию. Однако для комплексных сценариев сообщество предлагает открытые библиотеки. Одна из самых популярных — **`compose-test-rule-extra`** или кастомные правила, которые можно найти на GitHub. Они позволяют параметризовать тесты, запуская один и тот же сценарий для набора различных конфигураций.
Отдельного внимания заслуживает **тестирование навигации в Compose**. Поскольку навигация часто управляется состоянием (через `NavController`), мы можем тестировать ее, симулируя нажатия кнопок и проверяя текущий маршрут. Для изоляции навигации от реальных графов полезно использовать моки или тестовую реализацию `NavController`. Открытая практика — создание фабрики `TestNavHostController`, которая позволяет напрямую управлять стеком навигации в тестах.
Следующий шаг — **интеграция с скриншотным тестированием (Screenshot Testing)**. Это золотой стандарт для визуальной регрессии. Хотя официального инструмента от Google нет, сообщество создало отличные open-source решения. Лидер — **`paparazzi`** от Cash App (Square). Он работает на JVM, без эмулятора, что делает тесты невероятно быстрыми. Вы можете делать снимки ваших композейблов и сравнивать их с эталонными изображениями. Другой вариант — **`shot`** от Karumi, который работает на реальном устройстве/эмуляторе. Настройка `paparazzi`:
- Добавить плагин и зависимость в `build.gradle.kts`.
- Создать тест:
val paparazzi = Paparazzi()
paparazzi.snapshot {
Greeting(name = "Compose")
}
}`
При первом запуске создается эталон. При последующих — изображения сравниваются, и тест падает, если есть расхождения (что сигнализирует о непреднамеренных визуальных изменениях).
Наконец, **производительность и отладка тестов**. Медленные тесты убивают процесс разработки. Композ-тесты, работающие на эмуляторе, медленнее JVM-тестов (как `paparazzi`). Мастера рекомендуют разделять тесты: быстрые unit-тесты для логики состояний и ViewModel (без Compose Rule) и более тяжелые UI-тесты для интеграционных сценариев. Для отладки используйте метод `composeTestRule.onRoot().printToLog("TAG")`, который выводит в лог все семантическое дерево — бесценный инструмент, когда матчер не находит нужный узел.
В качестве best practice от сообщества: описывайте семантические свойства явно. Используйте модификатор `.semantics { }` и `contentDescription` для интерактивных элементов, которые не имеют текстовой метки. Это не только улучшит accessibility вашего приложения, но и сделает тесты стабильными.
Подводя итог, тестирование в Jetpack Compose — это не борьба, а естественное продолжение декларативного подхода. Используя встроенный Test API для проверки логики и взаимодействий и дополняя его открытыми инструментами вроде `paparazzi` для визуальной регрессии, вы можете построить надежный, быстрый и поддерживаемый тестовый конвейер. Это инвестиция, которая окупается уверенностью в каждом изменении UI и позволяет смело рефакторить код, не боясь сломать то, что пользователь видит на экране.
Комментарии (8)