Unit и UI тестирование Android‑приложений — практическое руководство
Короткий ответ: сначала покрывайте логику unit‑тестами (JVM), затем добавляйте UI‑тесты (Espresso/Compose) и запускайте всё в CI через Gradle; для масштабного прогонов используйте Firebase Test Lab. Ниже — пошагово и применимо прямо в проекте.
Оглавление {{TOC_AUTOMATIC}}
Что такое unit‑тесты и как их писать
Unit‑тесты проверяют маленькие единицы кода (методы, классы) без запуска UI и внешних сервисов. Плюсы: быстрые, пригодны для CI, дают раннюю обратную связь.
Как начать
- Разместите тесты в app/src/test/java.
- Используйте JUnit 5 (аннотации @Test, @BeforeEach) + assertEquals/assertThrows.
- Моки: Mockito или MockK (для Kotlin). Для корутин — runTest из kotlinx‑coroutines‑test.
Пример простого теста (Kotlin):
class CalculatorTest {
@Test
fun `calculateTotal with valid inputs returns correct sum`() {
val calc = Calculator()
val result = calc.calculateTotal(100.0, 0.2)
assertEquals(120.0, result, 0.001)
}
}
Покрытие и метрики
- Подключите JaCoCo: цель 70–90% для unit.
- Запускайте тесты локально и в CI (./gradlew testDebugUnitTest).
- Пишите тесты на граничные случаи: нули, отрицательные значения, null (если применимо).
Запускайте unit‑тесты перед коммитом — это экономит время на отладку и снижает вероятность регрессий.
UI‑тесты: Espresso, Compose Testing и окружение
UI‑тесты эмулируют действия пользователя: ввод, клики, навигацию. Они медленнее, но проверяют интеграцию экранов и поведение на устройстве.
Выбор инструмента
- Espresso — для классического View‑UI, надёжный для черного ящика.
- Compose Testing — если UI на Jetpack Compose; быстрый и позволяющий проверять семантику.
- UI Automator — для взаимодействия между приложениями или системных диалогов.
Где писать и запускать
- Код тестов: app/src/androidTest/java.
- Запуск: ./gradlew connectedAndroidTest для локальных девайсов/эмуляторов.
- Для CI/облачных прогонов: интеграция с Firebase Test Lab (runInstrumentationTests).
Примеры сценариев
- Логин: неверный пароль, повторные попытки, сохранение сессии.
- Навигация: правильная back‑логика и стейт при смене конфигурации.
- Edge: потеря сети, медленные ответы, orientation change.
Не игнорируйте flaky‑тесты: фиксация сидов, стабилизация таймаутов и минимизация зависимости от внешних сервисов критичны.
Инструменты и интеграция в CI/CD
Рекомендации по инструментам
- Unit: JUnit 5, Mockito/MockK, Kotest (для BDD/Kotlin‑нативных тестов).
- UI: Espresso, Compose Testing, UI Automator.
- Покрытие: JaCoCo.
- Облачное тестирование: Firebase Test Lab (множество устройств и API‑уровней).
Пример задач Gradle для CI
- Unit: ./gradlew testDebugUnitTest
- UI на подключённом устройстве: ./gradlew connectedAndroidTest
- Сбор покрытия: ./gradlew jacocoTestReport
CI‑стратегия
- Unit на каждом PR (быстро).
- UI: nightly или per‑release; критичные UI‑тесты можно запускать в PR, но экономичнее — smoke‑набор.
- Используйте матрицу устройств в CI/Firebase: Android API, экраны, density.
Частые ошибки
- Тестирование приватных методов — обычно лишнее; тестируйте поведение, не реализацию.
- Статические зависимости и глобальные синглтоны в коде — усложняют моки. Инвертируйте зависимости.
- Игнорирование многопоточности: для корутин используйте runTest и TestDispatcher.
- Ожидания по времени вместо явных синхронизаций в UI (используйте Idling Resources или composeTestRule.waitForIdle()).
- Недостаток стабильного тестового окружения — держите фиктивные ответы, локальные фейковые репозитории.
FAQ
- Какой порядок тестирования? Начните с unit, затем интеграционные и UI — это дает лучшее соотношение усилий к результату.
- Нужно ли 100% покрытие? Нет. Цель — покрытие критичных участков: бизнес‑логика и критичные сценарии; 70–90% по unit и 50%+ по UI — разумно.
- Как уменьшить flaky‑тесты? Зафиксируйте random seed, уберите внешние зависимости, используйте стабильные тестовые данные и idling mechanisms.
С такими практиками вы получите стабильные сборки и предсказуемые релизы: инвестируйте сначала в unit‑тесты (80% уверенности за 20% усилий), затем автоматизируйте UI‑прогоны и CI.