← Назад к вопросам

Какие классы покрываешь Unit-тестами

1.2 Junior🔥 241 комментариев
#Опыт и софт-скиллы#Тестирование

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Принципы выбора классов для Unit-тестов в Android разработке

Unit-тестирование — это фундаментальная практика в современной разработке, направленная на проверку корректности отдельных, изолированных единиц кода (чаще всего, классов или методов). В Android-проектах, из-за специфики платформы (сильная связь с фреймворком, жизненный цикл, UI), выбор объектов для юнит-тестирования требует стратегического подхода. Я покрываю тестами следующие ключевые категории классов, соблюдая баланс между покрытием кода, стоимостью поддержки и практической ценностью.

1. Классы бизнес-логики и чистые трансформации данных

Это первичный и самый важный объект для юнит-тестов. Такие классы не зависят от Android фреймворка, их легко изолировать.

  • Модели данных (User, Product) — тестируем методы преобразования, валидации, сравнения.
  • Сервисы и утилиты (DateFormatter, StringEncoder, Calculator) — проверяем корректность алгоритмов на различных входных данных.
  • Репозитории и источники данных (интерфейсы или их реализации, абстрагированные от Android) — тестируем логику маппинга, фильтрации, агрегации. Здесь часто используют Mockito для заглушек зависимостей.
// Пример: тест для утилиты форматирования
@Test
fun `formatPrice should return correct string with currency`() {
    val formatter = PriceFormatter()
    val result = formatter.formatPrice(1234.56, "USD")
    assertEquals("$1,234.56", result)
}

2. Presenters, ViewModels и другие классы из паттернов Presentation Layer

Классы, такие как ViewModel (MVVM) или Presenter (MVP), которые обрабатывают бизнес-логику для UI, но сами не являются View.

  • Тестируем реакцию на пользовательские события (например, метод onButtonClicked() должен изменять состояние).
  • Проверяем правильность преобразования данных из доменного слоя в состояние для UI.
  • Убеждаемся, что корректно вызываются зависимости (например, репозитории) и обрабатываются их ответы.
// Пример: тест для ViewModel с использованием Coroutines Test
@Test
fun `loadData should update state on success`() = runTest {
    val mockRepo = mock<Repository> { on { fetchData() } return Result.success(listOf()) }
    val viewModel = MyViewModel(mockRepo)
    viewModel.loadData()
    assertEquals(State.SUCCESS, viewModel.uiState.value)
}

3. Менеджеры, обработчики и координаторы

Классы, которые управляют процессом или состоянием внутри приложения.

  • NetworkManager (абстрагированный) — тестируем логику построения запросов, обработку кодов ответов (без реального сетевого вызова).
  • SessionManager, CacheManager — проверяем логику сохранения, очистки, восстановления данных.
  • NavigationCoordinator — тестируем логику определения следующего шага на основе условий.

4. Классы, реализующие контракты (интерфейсы)

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

Классы, которые я НЕ покрываю юнит-тестами (или покрываю минимально):

  1. Activity, Fragment, View (любые UI-классы). Они сильно зависят от контекста Android, жизненного цикла, инфраструктуры. Их тестирование — это область инструментальных (Integration/UI) тестов с использованием Espresso или Compose Testing. Юнит-тесты для них сложны, медленны и малоэффективны.
  2. Классы, являющиеся простыми данными или пассивными структурами (например, POJO/DTO без методов). Их тестирование не дает существенной ценности.
  3. Классы, которые являются прямыми обертками над внешними библиотеками или фреймворком (например, прямой вызов системного API). Здесь фокус должен быть на интеграционном тестировании.

Критерии и принципы принятия решения:

  • Принцип "Тестируем публичное поведение, не реализацию": Тесты должны проверять контракт класса (что он делает), а не его внутренние приватные методы. Это позволяет рефакторить код без переписывания тестов.
  • Соотношение стоимости и пользы: Я оцениваю, насколько класс критичен для функциональности, часто изменяется или содержит сложную логику. Высокая сложность → высокий приоритет для тестов.
  • Возможность изоляции: Если класс можно легко отделить от Android Framework с помощью зависимостей через интерфейсы и заглушек (Mock), он кандидат на юнит-тест. Если изоляция требует огромных усилий — это сигнал к возможному рефакторингу архитектуры.
  • Следование архитектурным слоям: Я стремлюсь к высокому покрытию тестами в доменном слое (Domain Layer) и слое бизнес-логики (Business Layer), где находится ядро приложения. Слой представления (UI) и данные (Data) тестируются соответствующими типами тестов.

Итоговый подход: Юнит-тесты — это инвестиция в стабильность бизнес-логики и доменной модели. Они создают "защитную сетку" при рефакторинге, позволяют быстро обнаруживать регрессии и служат живой документацией поведения системы. Выбор классов для тестирования — это непрерывный процесс анализа архитектуры, рисков и стоимости, направленный на максимизацию качества и скорости разработки.