Какие классы покрываешь Unit-тестами
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Принципы выбора классов для 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. Классы, реализующие контракты (интерфейсы)
Юнит-тесты идеально подходят для проверки, что конкретная реализация соблюдает ожидаемое поведение, описанное интерфейсом. Это гарантирует заменяемость компонентов и стабильность архитектуры.
Классы, которые я НЕ покрываю юнит-тестами (или покрываю минимально):
- Activity, Fragment, View (любые UI-классы). Они сильно зависят от контекста Android, жизненного цикла, инфраструктуры. Их тестирование — это область инструментальных (Integration/UI) тестов с использованием Espresso или Compose Testing. Юнит-тесты для них сложны, медленны и малоэффективны.
- Классы, являющиеся простыми данными или пассивными структурами (например, POJO/DTO без методов). Их тестирование не дает существенной ценности.
- Классы, которые являются прямыми обертками над внешними библиотеками или фреймворком (например, прямой вызов системного API). Здесь фокус должен быть на интеграционном тестировании.
Критерии и принципы принятия решения:
- Принцип "Тестируем публичное поведение, не реализацию": Тесты должны проверять контракт класса (что он делает), а не его внутренние приватные методы. Это позволяет рефакторить код без переписывания тестов.
- Соотношение стоимости и пользы: Я оцениваю, насколько класс критичен для функциональности, часто изменяется или содержит сложную логику. Высокая сложность → высокий приоритет для тестов.
- Возможность изоляции: Если класс можно легко отделить от Android Framework с помощью зависимостей через интерфейсы и заглушек (Mock), он кандидат на юнит-тест. Если изоляция требует огромных усилий — это сигнал к возможному рефакторингу архитектуры.
- Следование архитектурным слоям: Я стремлюсь к высокому покрытию тестами в доменном слое (Domain Layer) и слое бизнес-логики (Business Layer), где находится ядро приложения. Слой представления (UI) и данные (Data) тестируются соответствующими типами тестов.
Итоговый подход: Юнит-тесты — это инвестиция в стабильность бизнес-логики и доменной модели. Они создают "защитную сетку" при рефакторинге, позволяют быстро обнаруживать регрессии и служат живой документацией поведения системы. Выбор классов для тестирования — это непрерывный процесс анализа архитектуры, рисков и стоимости, направленный на максимизацию качества и скорости разработки.