Unit-тестирование Android-приложений: что это и как провести?
Unit-тестирование – один из ключевых инструментов качества в Android-разработке. По данным MoldStud (2025), команды, применяющие моки для изоляции зависимостей, запускают тесты до 70% быстрее по сравнению с тестами, обращающимися к реальным сетевым слоям. При этом 30% снижение дефектов фиксируется у команд, интегрировавших тесты в CI/CD-пайплайн. В этой статье – практическое руководство: от базовых понятий до написания первого теста с MockK и JUnit.
Что такое unit-тестирование в Android?
Юнит тестирование это проверка отдельной единицы кода – одной функции, метода или класса – в полной изоляции от остальной системы. В Android такой «единицей» чаще всего выступает ViewModel, UseCase или Repository. Unit-тест не запускает эмулятор, не обращается к базе данных и не делает сетевых запросов – он работает только с логикой конкретного класса, а все зависимости заменяются мок-объектами.
| Тип теста | Что проверяет | Где запускается | Скорость |
| Unit-тест | Логику одного класса/метода | JVM (без Android) | Миллисекунды |
| Интеграционный | Взаимодействие нескольких компонентов | JVM / эмулятор | Секунды |
| UI (Espresso) | Поведение интерфейса с точки зрения пользователя | Эмулятор / устройство | Секунды-минуты |
Пример: у вас есть LoginViewModel, который принимает email и пароль, валидирует их и вызывает AuthRepository. Unit-тест проверяет только логику валидации – без реального репозитория и без UI. AuthRepository заменяется моком, который возвращает нужный результат.
Зачем нужны unit-тесты в Android-разработке?
Зачем нужны unit тесты – вопрос, который задают в начале проекта и перестают задавать после первого крупного рефакторинга без покрытия. Вот пять конкретных причин:
- Быстрый фидбек. Тест на JVM выполняется за миллисекунды – в отличие от сборки и запуска на эмуляторе. Разработчик получает сигнал о баге сразу после изменения кода, а не через 2-3 минуты сборки.
- Безопасный рефакторинг. Если все unit-тесты проходят после изменений – поведение системы не нарушено. Без тестов рефакторинг превращается в угадывание: что сломалось, а что нет.
- Документация через код. Хорошо написанный тест – это спецификация поведения класса. Новый разработчик читает тест и понимает, что должен делать метод, без необходимости читать всю бизнес-логику. Это особенно ценно в больших Android-проектах, где правильная архитектура интерфейса закладывается с самого начала – подробнее об этом в материале «Интерфейс мобильных приложений: от А до Я».
- Снижение стоимости багов. Баг, найденный на этапе юнит-теста, дешевле в исправлении в 5-10 раз по сравнению с багом, найденным в продакшене (по данным IBM NIST Study, классический показатель стоимости дефекта по фазе).
- CI/CD-интеграция. Unit-тесты запускаются автоматически при каждом пул-реквесте. Это предотвращает мерж кода, который ломает существующую логику, без участия человека в проверке.
Какие виды тестов существуют и где место unit-тестов?
Unit тесты это основание тестовой пирамиды – их должно быть больше всего, они должны выполняться быстрее всего и покрывать максимальную часть бизнес-логики.
Google рекомендует соотношение 70/20/10: 70% unit-тестов, 20% интеграционных, 10% UI. На практике многие команды начинают с нуля и двигаются к этому соотношению итеративно, начиная с покрытия ViewModel и UseCase.
Правило: если тест требует запуска на эмуляторе или устройстве – это уже не unit-тест. Unit-тест работает исключительно на JVM, без Android-зависимостей. Все, что требует Context, Activity или Fragment, тестируется через Robolectric или в androidTest.
Что тестировать, а что – нет?
Unit тесты что это в контексте Android – это тесты бизнес-логики, а не фреймворка. Юнит тесты это всегда про то, что делает ваш код, а не как устроен код фреймворка. Хорошее правило: тестируйте то, что написали вы, а не то, что написал Google или JetBrains.
| Тестировать | Не тестировать |
| Логику ViewModel (состояние, события) | Стандартные классы SDK (Activity, Fragment) |
| UseCase / Interactor | Сторонние библиотеки (Retrofit, Room) |
| Маппинг данных (DTO → Domain) | Автогенерированный код |
| Валидацию форм и бизнес-правила | Простые геттеры/сеттеры без логики |
| Обработку ошибок и edge cases | Константы и data-классы без поведения |
Пример хорошего кандидата для теста: метод calculateDiscount(price, userType) в вашем прайсинговом UseCase. Пример плохого кандидата: геттер getName() в data-классе User без какой-либо логики.
Инструменты для unit-тестирования Android-приложений
Стандартный стек для unit-тестирования Android в 2024-2025 году:
| Инструмент | Назначение | Когда использовать |
| JUnit 4 / JUnit 5 | Базовый фреймворк: @Test, @Before, @After, assertions | Всегда – основа любого unit-теста |
| Mockito + mockito-kotlin | Создание моков, стаббинг, верификация вызовов | Java/Kotlin-проекты, стандартный мокинг |
| MockK | Мокинг-фреймворк для Kotlin: object, companion, coroutines | Kotlin-проекты, тесты корутин и Flow |
| Turbine | Тестирование Kotlin Flow и StateFlow | Когда ViewModel использует Flow |
| kotlinx-coroutines-test | TestDispatcher, runTest для тестов корутин | Любой асинхронный код на корутинах |
| Robolectric | JVM-симуляция Android-среды без эмулятора | Когда нужен Context, но не UI |
Зависимости для build.gradle (Kotlin DSL):
testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.13.12")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1")
testImplementation("app.cash.turbine:turbine:1.1.0")
Официальная документация по локальным тестам доступна на стнанице developer.android.com/training/testing/local-tests, а детальное сравнение MockK и Mockito для Kotlin: MockK vs Mockito и Mockito.
Структура проекта: куда писать тесты?
Unit тесты Android-проекта живут в строго определенных директориях – и путать их нельзя:
| Директория | Тип тестов | Где запускается | Пример |
| app/src/test/java | Unit-тесты (local tests) | JVM, без Android | ViewModelTest, UseCaseTest |
| app/src/androidTest/java | Инструментальные тесты | Эмулятор / устройство | EspressoTest, RoomTest |
app/src/test/java – здесь живут unit-тесты. Они не требуют Android SDK, запускаются на чистой JVM. Файл LoginViewModelTest.kt должен быть именно здесь. Если случайно положить unit-тест в androidTest – он будет требовать эмулятор и работать в 10–20 раз медленнее.
app/src/androidTest/java – инструментальные тесты: Espresso, тесты Room с реальной базой, интеграционные тесты через Hilt. Они медленнее, но позволяют тестировать компоненты, которые невозможно проверить без Android-окружения.
Правило: если тест можно написать без Context и без Android-зависимостей – он относится в test/java. Если нет – в androidTest/java.
Как написать unit-тест?
Разберем написание unit тестов на примере ViewModel с репозиторием. Допустим, есть UserViewModel, который загружает пользователя по ID через UserRepository. Написание unit тестов начинается с паттерна AAA: Arrange → Act → Assert.
class UserViewModelTest {
private val repository = mockk()
private lateinit var viewModel: UserViewModel
@Before
fun setup() {
viewModel = UserViewModel(repository)
}
@Test
fun `loadUser emits user when repository returns success`() = runTest {
// Arrange
val user = User(id = 1, name = "Anna")
coEvery { repository.getUser(1) } returns Result.success(user)
// Act
viewModel.loadUser(1)
// Assert
assertEquals(user, viewModel.uiState.value.user)
}
}
Что здесь происходит: создается мок репозитория через mockk<>(). В методе setup() инициализируется ViewModel с мок-зависимостью. В тесте через coEvery задается поведение мока – что вернуть при вызове getUser(1). Затем вызывается тестируемый метод и через assertEquals проверяется результат.
Как писать unit тесты для Flow и StateFlow – используйте Turbine. Вместо assertEquals на .value, собирайте эмиссии через turbine.awaitItem():
viewModel.uiState.test {
val item = awaitItem()
assertEquals(user, item.user)
cancelAndIgnoreRemainingEvents()
}
Частые ошибки при написании unit-тестов
Модульное тестирование это не просто запись кода с аннотацией @Test. Типичные ошибки, которые снижают ценность тестов:
| Ошибка | Последствие | Как правильно |
| Тестировать реализацию, а не поведение | Тест ломается при любом рефакторинге, даже если поведение не изменилось | Проверяйте входные данные → выходные данные, а не порядок вызовов методов |
| Использовать relaxed = true в MockK везде | Пропущенные NPE и скрытые баги – мок молча возвращает дефолтные значения | Явно задавайте поведение через coEvery/every для каждого вызова |
| Один тест проверяет несколько сценариев | При падении непонятно, что сломалось; тест сложно читать | Один тест – один сценарий. Используйте параметризованные тесты для похожих случаев |
| Не проверять edge cases и ошибки | Продакшн падает на пустых списках, null и сетевых ошибках | Всегда пишите тесты на success + error + empty state |
| Класть unit-тесты в androidTest/ | Тесты запускаются на эмуляторе – медленно, нестабильно, не в CI | Unit-тесты – только в src/test/java, без Android-зависимостей |
Заключение
Unit тестирование – это инвестиция, которая окупается с каждым рефакторингом, новой фичей и исправлением бага. Правильно выстроенный стек (JUnit + MockK + Turbine + coroutines-test) и четкое разделение test/ vs androidTest/ делают написание тестов быстрым и предсказуемым. Начните с ViewModel и UseCase – это принесет максимальный эффект при минимальных усилиях. Если вы планируете Android-приложение с нуля и хотите заложить правильную архитектуру с первого дня, ознакомьтесь с вариантами разработки мобильных приложений на Android под ключ.
Да – особенно в небольшом. Чем меньше проект, тем быстрее написать тесты и тем болезненнее каждый необнаруженный баг. Минимальный набор: тест для каждого UseCase и ViewModel. Даже 20-30 тестов дают уверенность при рефакторинге. Полезный обзор инструментов для разработки Android/iOS с нуля – в материале «Топ-10 программ для создания приложений на Android и iOS».
После прохождения всех тестов и финальной сборки – в Google Play. Пошаговый процесс публикации описан в материале «Как опубликовать приложение в Google Play». Если нужна помощь с архитектурой, тестированием и выпуском – разработка приложений под ключ включает все этапы от проектирования до публикации.




