Разработка Unit-тестов для Android-приложения (JUnit 5, MockK)

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

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1734 услуг
Разработка Unit-тестов для 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