Розробка юніт-тестів для Android-додатку (JUnit 5, MockK)

Логіка додатку ламається непомітно: правка в одному місці тягне за собою збої в іншому, а регресії виникають уже в користувачів. Ми розробляємо юніт-тести для Android під ключ — від аналізу критичних сценаріїв до готового набору перевірок у вашому проєкті. Покриття ключових модулів скорочує частку регресій у рази та прискорює випуск оновлень.

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Розробка юніт-тестів для Android-додатку (JUnit 5, MockK)
Середній
~3-5 днів
Часті запитання

Юніт-тести Android: де ламається логіка і як це ловити

Логіка Android-застосунку ламається не у верстці, а в асинхронних сценаріях. Екран відкрився, користувач пішов у фон, повернувся — і бачить застарілі дані, бо відповідь мережі прийшла після скасування запиту.

Такі дефекти не відтворюються вручну стабільно: вони залежать від порядку подій, швидкості мережі та життєвого циклу.

Юніт-тести Android фіксують саме цей порядок — підміняють мережу й сховище, подають відповіді в потрібній послідовності та перевіряють, що стан екрана набуває очікуваного вигляду.

Друге вузьке місце — стан екрана. Завантаження, помилка, порожній результат і успіх часто змішуються в одному флазі, і при швидкому перемиканні інтерфейс показує комбінацію, якої не повинно бути: спінер поверх помилки або порожній список замість даних із кешу. Тест на переходи станів ловить такі комбінації до того, як їх побачить користувач.

Третє — обробка помилок і кеш. Що відбувається при таймауті, при невалідній відповіді, при розсинхронізації локальних даних із сервером? Без покриття логіки ці гілки залишаються неперевіреними, і перша ж нештатна ситуація йде в реліз як регресія.

Юніт-тести проганяють кожну гілку ізольовано — без емулятора й пристрою, за секунди, тому їх можна ганяти на кожну зміну.

Сенс не у відсотках покриття заради звітності, а в тому, які класи відмов закриті. Ми покриваємо насамперед асинхронні сценарії, переходи стану екрана, роботу з кешем і обробку помилок — те, що найчастіше ламається при доопрацюваннях. Далі розбираємо, як це влаштовано в проєкті.

Що дає покриття тестами: вимірюваний ефект

Юніт-тести Android дають команді те, чого не дає жоден етап ручного тестування — зворотний зв'язок за секунди замість годин.

Поки збірка та прогін на емуляторі тривають хвилинами, набір модульних перевірок на JUnit і MockK відпрацьовує за час, порівнянний із компіляцією, і розробник дізнається про поломку одразу після збереження файлу, а не на етапі регресії перед викладкою.

Це змінює саму механіку роботи: правка в бізнес-логіці перестає бути ризиком, бо її наслідки видно негайно.

Частка дефектів, які виявляються до код-рев'ю, зростає передбачувано — граничні значення, порожні відповіді, помилки парсингу та гілки обробки винятків закриваються тестами на рівні функцій і класів.

Чим щільніше покриті сценарії ViewModel і шару роботи з даними, тим менше помилок доходить до збірки і тим рідше реліз гальмується через знахідки на прийманні.

Стабільність релізів — прямий наслідок. Коли логіка зафіксована тестами, рефакторинг і оновлення залежностей перестають бути лотереєю: будь-яка розбіжність у поведінці спливає на прогоні, а не в користувачів. Відкат і повторна викладка потрібні рідше, а сам цикл випуску стає коротшим.

Ми вибудовуємо покриття там, де воно окупається: критичні сценарії, розрахунки, робота з мережею та сховищем — під тестами, другорядні екрани не гальмують роботу команди зайвими перевірками. Опишіть задачу — повернемося з оцінкою та планом покриття під ваш проєкт.

Підходи до тестування: порівняння варіантів

Вибір підходу визначається тим, що саме перевіряємо і наскільки дорого нам дається зворотний зв'язок. Чисті юніт-тести ізолюють логіку без Android-оточення і дають результат за секунди, але не ловлять помилки стикування шарів.

Тести з підміною залежностей закривають взаємодію з мережею, сховищем і системними сервісами, залишаючись швидкими. Інтеграційні перевірки збирають реальні компоненти і підтверджують, що модулі працюють разом — ціною часу прогону і крихкості до змін оточення.

Підхід Коли підходить Обмеження
Чисті юніт-тести Бізнес-логіка, розрахунки, розбір відповідей, граничні умови Не перевіряють реальні залежності та поведінку платформи
Тести з підміною залежностей Сценарії з мережею, базою, системними сервісами; перевірка розгалужень і помилок Підміна може розходитися з реальною поведінкою залежності
Інтеграційні перевірки Стикування шарів, робота зі сховищем, наскрізні сценарії Довгий прогін, чутливість до оточення, складніше локалізувати збій

Як поєднувати підходи

Практика — піраміда: основа з чистих юніт-тестів на бізнес-логіку, середній шар з підміною залежностей на взаємодії, вершина з інтеграційних перевірок на критичні шляхи.

Такий розподіл тримає прогін швидким, а покриття осмисленим: більшість дефектів відловлюється на нижніх шарах, де їх дешевше виправляти.

Обмеження, які важливо врахувати

Жоден підхід не замінює решту. Чисті тести не зловлять несумісність версій бібліотек, підміна — розходження контракту з реальним сервісом, інтеграційні перевірки — рідкісні граничні випадки логіки. Тому набір будується під ризики конкретного застосунку, а не за шаблоном.

Як ми впроваджуємо тести: етапи роботи

Впровадження тестів починається не з написання коду, а з інвентаризації: які модулі взагалі існують, де зосереджена логіка, а де — лише розмітка екранів.

На цьому етапі ми дивимося історію змін і знаходимо класи, які правляться найчастіше: саме там помилки коштують найдорожче, а тести дають найбільший ефект.

  1. Аудит коду. Розбираємо структуру проєкту, залежності між модулями, якість розділення шарів. Відзначаємо місця, де логіка перемішана зі зверненням до мережі або бази, — їх доведеться трохи розділити, інакше тест не ізолювати без зовнішніх систем.
  2. Схема покриття. Фіксуємо, що саме перевіряємо: розрахунки, перетворення даних, обробку помилок, граничні значення. Домовляємося про цільову частку покриття за критичними модулями — не «все підряд», а там, де ціна помилки вища.
  3. Налаштування середовища. Піднімаємо тестовий контур у збірці: окремий набір задач, звіт про покриття, автоматичний запуск на кожну зміну. Тести, які потрібно запускати вручну, перестають запускати через два тижні.
  4. Написання тестів. Йдемо від критичного до другорядного. Кожен тест перевіряє одну поведінку і не залежить від порядку запуску. Зовнішні залежності підмінюємо керованими двійниками, тому набір проходить за секунди і не потребує мережі.
  5. Перевірка на реальних сценаріях. Прогоняємо набір на змінах, які вже ламали застосунок раніше, — переконуємося, що тести їх ловлять. Тест, який завжди зелений, гірший за відсутній: він створює хибну впевненість.
  6. Передача. Віддаємо набір, інструкцію із запуску, правила додавання нових тестів і розбір типових помилок. Проводимо розбір із вашою командою, щоб підтримка не залежала від нас.

Ключовий критерій на виході — не кількість тестів, а час до виявлення регресії: помилка має спливати на збірці, а не в магазині застосунків. Опишіть задачу — повернемося з оцінкою та планом за вашим проєктом.

Артефакти та регламенти: що залишається в проєкті

Після здачі проєкту в репозиторії залишається не «набір тестів», а робочий контур перевірок, який можна запускати без автора. Нижче — що саме передаємо на руки і як цим користується команда замовника.

  • Тестові набори за шарами. Домен і бізнес-логіка покриті швидкими тестами без Android-залежностей, робота зі сховищем і мережею — окремими наборами з підміною зовнішніх викликів. Кожен набір запускається ізольовано, тому падіння локалізується за секунди, а не за прогін усього проєкту.
  • Конфігурації запуску. Готові профілі для локального прогону, для перевірок на кожній зміні та для нічної збірки з повним покриттям. Розробник запускає потрібний профіль однією командою, без ручного налаштування середовища.
  • Схеми залежностей. Діаграми, що показують, які модулі від чого залежать і де проходить межа підміни. За ними видно, чому тест падає при зміні конкретного класу, і куди додавати нові перевірки.
  • Звіти про покриття. Налаштований зведений звіт за модулями з порогом, нижче якого збірка не проходить. Видно, які гілки логіки не перевірені, — це список задач, а не абстрактна цифра.
  • Документація. Угоди про іменування тестів, правила оформлення тестових даних, опис того, що вважається достатнім покриттям для нового коду.
  • Регламенти запуску. Коли тести обов'язкові, що блокує злиття змін, хто розбирає упалі перевірки і в який термін. Правила зафіксовані в репозиторії, а не в листуванні.
  • Інструкція з супроводу. Як додати тест під нову функціональність, як оновлювати підміни при зміні зовнішніх сервісів, що робити при нестабільному тесті.

Усе це лежить поруч із кодом і версіонується разом із ним. Новий інженер у команді розгортає середовище і отримує зелений прогін першого ж дня — без усних пояснень від автора.

Кейс: скорочення регресій після впровадження тестів

Проєкт — Android-застосунок з оплатою та підписками, реліз раз на два тижні. Тести були, але писалися через раз і здебільшого на «чисті» функції: логіка оплати, розрахунку знижок і відновлення підписки залишалася непокритою.

Регресії ловили на ручному прогоні перед викладкою, і приблизно кожна четверта доходила до користувачів — найчастіше у вигляді повторних списань і «завислих» статусів замовлення.

Що змінили

Розділили логіку та платформний код: бізнес-правила винесли в окремі класи без залежностей від Android-оточення, а зовнішні сервіси — оплату, сховище, мережу — закрили інтерфейсами.

Далі на MockK описали поведінку цих інтерфейсів, включно з відмовами та таймаутами, а JUnit 5 дав параметризовані перевірки: один набір граничних значень замість десятка майже однакових методів.

Тести ганяються на кожен коміт, тому збірка падає до рев'ю, а не після. Окремо зафіксували правило: нова гілка логіки оплати не проходить перевірку, якщо не покрита сценаріями помилок — не лише успішним шляхом.

Метрики до і після

Показник До Після
Частка дефектів, що дійшли до користувачів ~25% ~6%
Ручна перевірка релізу 2 дні 4-6 годин
Збірки з падінням на інтеграції кожна третя поодинокі
Покриття логіки оплати фрагментарне основні та помилкові сценарії

Ручний прогін скоротився не тому, що перевірок стало менше, а тому що критичні шляхи тепер відсікаються автоматично.

Час, що звільнився, пішов на перевірку того, що тестами не закривається: поведінка на реальних пристроях, робота з платіжними діалогами, відновлення після згортання застосунку.

Через два релізи частка дефектів стабілізувалася на новому рівні, а відкат версії перестав бути штатним сценарієм.

Чим юніт-тести відрізняються від інтеграційних?

Юніт-тести перевіряють один модуль в ізоляції: усі зовнішні залежності — репозиторії, мережеві клієнти, системні сервіси — підмінюються керованими двійниками. Запускаються такі перевірки на JVM, без емулятора й пристрою, тому весь набір із сотень сценаріїв проходить за секунди.

Ізоляція модулів тут не самоціль: вона прибирає хибні падіння, коли тест падає через недоступний сервер або чужий код, а не через помилку в логіці, яку перевіряють.

Інтеграційні тести працюють на стику модулів і перевіряють, що вони справді стикуються: серіалізація запиту й розбір відповіді, поведінка кешу при зміні даних, коректність запитів до локальної бази.

Тут уже піднімається реальне оточення — тестова база, підставний сервер, іноді емулятор. Такі перевірки тривають довше й падають частіше, зате ловлять клас помилок, який ізольований тест у принципі не побачить.

Межа перевірок проходить по питанню «чия це відповідальність». Логіка розрахунку, валідація полів, розбір станів екрана, перетворення моделей — це юніт-тести, бо результат залежить лише від вхідних даних.

Взаємодія з мережею, базою, файловою системою, дозволами й життєвим циклом — інтеграційні, бо результат залежить від оточення. Змішувати їх в одному наборі не можна: втрачається швидкість і точність локалізації помилки.

На практиці набір будується пірамідою: основна маса швидких ізольованих перевірок на бізнес-логіку, поверх — інтеграційні на критичні стики, і трохи наскрізних сценаріїв на головні користувацькі шляхи.

Такий розподіл дає ранній зворотний зв'язок у процесі розробки й не перетворює прогін перед релізом на багатогодинне очікування. Якщо межа розмита й усе звалюється в інтеграційні тести, команда перестає запускати їх локально — і перевірки перестають захищати код.

Часті технічні сумніви

Тестованість Android-додатка рідко впирається в сам фреймворк — вона впирається в архітектуру, яка склалася до того, як про тести взагалі задумалися.

Нижче — чотири питання, які технічні керівники ставлять найчастіше, і те, як ми на них відповідаємо на рівні коду, а не загальних слів.

У нас легасі-код без тестів — з чого ви починаєте?

Спочатку фіксуємо поточну поведінку тестами-характеристиками: покриваємо найбільш навантажені сценарії так, як вони працюють зараз, включно з дивацтвами. Це страховка перед рефакторингом — після кожної зміни прогін показує, що поведінка не поїхала.

Далі виносимо логіку з актівіті та фрагментів в окремі класи, які можна перевірити без емулятора: тест на чистій логіці виконується за частки секунди, і весь прогін перестає бути вузьким місцем збірки.

Як тестувати асинхронність і корутини, щоб тести не «блимали»?

Підмінюємо диспетчери на тестовий планувальник і просуваємо віртуальний час вручну — тест перестає залежати від реальних затримок і мережевих таймаутів.

Асинхронний код перевіряється детерміновано: один і той самий прогін дає один і той самий результат і на локальній машині, і на сервері збірки. Окремо покриваємо скасування корутин і обробку помилок — саме там найчастіше ховаються дефекти, які не ловляться ручним тестуванням.

Наскільки глибоко ви підмінюєте залежності?

Ми ізолюємо зовнішні межі — мережу, базу, файлову систему, системний час — і залишаємо справжньою ту логіку, яку перевіряємо. Мок на кожен чх робить тест крихким: він падає при невинному рефакторингу і нічого не говорить про поведінку.

Підміна залежностей будується на інтерфейсах, тому тест переживає зміну мережевого клієнта або сховища без переписування.

Хто підтримує тести після здачі проєкту?

Тести — частина кодової бази, а не окремий артефакт: вони лежать поруч із кодом, проходять рев'ю в тому самому запиті на злиття і запускаються на кожній зміні.

Ми передаємо команді угоди про структуру тестів і розбір типових сценаріїв, щоб новий розробник писав їх у тому самому стилі. Якщо тест застаріває разом із вимогою — він правиться або видаляється, а не вимикається назавжди.

Опишіть вашу кодову базу та поточне покриття — повернемося з оцінкою та планом, які ділянки закривати в першу чергу.

Обговоримо задачу та повернемося з планом

Щоб повернутися з планом, а не із загальними словами, нам потрібно зрозуміти будову вашого Android-проєкту та поточний стан тестів.

Надішліть доступ до репозиторію або вивантаження модулів — подивимося, як розбито код за шарами, де проходить межа з мережею та сховищем, які залежності впроваджуються через конструктори, а які тягнуться статикою.

Від цього залежить, де юніт-тести ляжуть без переписування продакшн-коду, а де потрібно спочатку розв'язати пов'язані компоненти.

Окремо уточнимо вимоги: які сценарії для вас критичні (розрахунок бізнес-логіки, обробка помилок мережі, кеш, валідація), який рівень покриття вважаєте достатнім і чи є вже тести, які не можна ламати.

Якщо в проєкті використовується DI-контейнер та асинхронність на корутинах, скажіть — під неї налаштовується тестовий диспетчер, щоб перевірки йшли детерміновано, без випадкових падінь.

Що підготуємо до наступного кроку:

  • карту модулів і шарів, де тести дають максимальний ефект при мінімальних правках;
  • перелік класів під перевірку з пріоритетом за ризиком регресій;
  • схему моків для зовнішніх залежностей — мережа, сховище, системний час;
  • правила ізоляції тестів, щоб вони не залежали від порядку запуску;
  • конфігурацію запуску в збірці та звіт про покриття;
  • оцінку обсягу робіт за етапами з точками проміжної приймання;
  • план впровадження перевірок у конвеєр збірки, щоб регресії ловилися до релізу.

Опишіть задачу у вільній формі: що за застосунок, на якій стадії розробки, що болить зараз — часті регресії, страх перед рефакторингом або підготовка до передачі коду іншій команді. Цього достатньо, щоб повернутися з оцінкою, планом робіт і пропозицією щодо наступного кроку.

Наші компетенції:

Останні роботи

  • Розробка мобільного додатка для компанії FEEDME
    Розробка мобільного додатка для компанії FEEDME
    1009
  • Розробка мобільного додатку для компанії XOOMER
    Розробка мобільного додатку для компанії XOOMER
    902
  • Розробка мобільного додатку для компанії RHL
    Розробка мобільного додатку для компанії RHL
    1331
  • Розробка мобільного додатку для компанії ZIPPY
    Розробка мобільного додатку для компанії ZIPPY
    1179
  • Розробка мобільного додатку для компанії Affhome
    Розробка мобільного додатку для компанії Affhome
    1106
  • Розробка мобільного додатку для компанії FLAVORS
    Розробка мобільного додатку для компанії FLAVORS
    713