Unit-тестування Android-застосунків: що це і як провести?

22
7 хв.

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-тести – питання, яке ставлять на початку проєкту і перестають ставити після першого великого рефакторингу без покриття. Ось п'ять конкретних причин:

  1. Швидкий фідбек. Тест на JVM виконується за мілісекунди – на відміну від збірки та запуску на емуляторі. Розробник отримує сигнал про баг одразу після зміни коду, а не через 2-3 хвилини збірки.
  2. Безпечний рефакторинг. Якщо всі unit-тести проходять після змін – поведінка системи не порушена. Без тестів рефакторинг перетворюється на вгадування: що зламалося, а що ні.
  3. Документація через код. Добре написаний тест – це специфікація поведінки класу. Новий розробник читає тест і розуміє, що має робити метод, без потреби читати всю бізнес-логіку. Це особливо цінно у великих Android-проєктах, де правильна архітектура інтерфейсу закладається з самого початку – детальніше про це в матеріалі «Інтерфейс мобільних застосунків: від А до Я».
  4. Зниження вартості багів. Баг, знайдений на етапі юніт-тесту, дешевший у виправленні в 5–10 разів порівняно з багом, знайденим у продакшні (за даними IBM NIST Study, класичний показник вартості дефекту за фазою).
  5. 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, coroutinesKotlin-проєкти, тести корутин і Flow
TurbineТестування Kotlin Flow і StateFlowКоли ViewModel використовує Flow
kotlinx-coroutines-testTestDispatcher, runTest для тестів корутинБудь-який асинхронний код на корутинах
RobolectricJVM-симуляція 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 та Mockito.

Структура проєкту: куди писати тести?

Unit-тести Android-проєкту живуть у строго визначених директоріях – і плутати їх не можна:

ДиректоріяТип тестівДе запускаєтьсяПриклад
app/src/test/javaUnit-тести (local tests)JVM, без AndroidViewModelTest, 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.awaitItem(). Замість 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/Тести запускаються на емуляторі – повільно, нестабільно, не в CIUnit-тести – лише в src/test/java, без Android-залежностей

Висновок

Юніт-тестування – це інвестиція, яка окупається з кожним рефакторингом, новою фічею та виправленням бага. Правильно вибудований стек (JUnit + MockK + Turbine + coroutines-test) і чіткий розподіл test/ vs androidTest/ роблять написання тестів швидким і передбачуваним. Починайте з ViewModel і UseCase – це принесе максимальний ефект за мінімальних зусиль. Якщо ви плануєте Android-застосунок з нуля і хочете закласти правильну архітектуру з першого дня, ознайомтеся з варіантами розробки мобільних застосунків на Android під ключ.

Часті запитання

Так – особливо в невеликому. Чим менший проєкт, тим швидше написати тести і тим болючіший кожен не виявлений баг. Мінімальний набір: тест для кожного UseCase і ViewModel. Навіть 20–30 тестів дають впевненість при рефакторингу. Корисний огляд інструментів для розробки Android/iOS з нуля – у матеріалі «Топ-10 програм для створення застосунків на Android та iOS».

Правий клік на папці test → Run Tests. Або через термінал: ./gradlew test. Для конкретного класу: ./gradlew testDebugUnitTest --tests "com.example.LoginViewModelTest". Результати з'являються в панелі Run – зелений = пройшов, червоний = упав із деталями.

Після проходження всіх тестів і фінальної збірки – в Google Play. Покроковий процес публікації описано в матеріалі «Як опублікувати застосунок у Google Play». Якщо потрібна допомога з архітектурою, тестуванням і випуском – розробка застосунків під ключ включає всі етапи від проєктування до публікації.

11 вересня 2026
5 / 5 (1 голос)