---
name: testing
description: Пиши тесты в андроид-проекте правильно. Используй этот навык при написании любых типов тестов (unit, integration, UI), тестировании бизнес-логики и компонентов UI.
---

# Тестирование

## When to Use

- Используй этот навык, когда пишешь unit-тесты для бизнес-логики (ViewModels, Use Cases, Domain models)
- Используй этот навык, когда пишешь интеграционные тесты для DAO и Repository
- Используй этот навык, когда пишешь UI тесты для Compose компонентов
- Используй этот навык, когда тестируешь Flow/StateFlow с использованием Turbine
- Используй этот навык, когда нужно протестировать обработку исключений в корутинах
- Этот навык полезен при выборе правильного типа теста для конкретного сценария
- Этот навык помогает определить, когда использовать unit-тесты с моками, а когда интеграционные тесты

## Типы тестов

- **Unit**: бизнес-логика изолированно, MockK для зависимостей, AAA паттерн
- **Integration**: взаимодействие слоев (DAO, Repository), реальные реализации БД
- **UI**: критические сценарии, Compose Testing для компонентов

## Инструменты

- JUnit 5 - unit-тесты
- MockK - мокирование
- Compose Testing - Compose компоненты
- Room Testing - для интеграционных тестов БД
- kotlinx-coroutines-test - для тестирования корутин
- Turbine - для тестирования Flow/StateFlow (app.cash.turbine:turbine:1.1.0)

## Запуск тестов и отчеты

### Команда make test

Используйте команду `make test` для запуска всех unit-тестов:

```bash
make test
```

Эта команда:

1. Запускает `./gradlew test --console=plain` - все unit-тесты (JVM, без устройства)
2. Автоматически выполняет скрипт `scripts/test_report.py` после завершения тестов
3. Показывает детальный отчет со статистикой по тестовым классам

### Скрипт test_report.py

Скрипт `scripts/test_report.py` генерирует детальный отчет о результатах тестов:

- Показывает общую статистику: всего тестов, успешные, упавшие
- Выводит список всех упавших тестов с именами классов и методов
- Отображает таблицу статистики по тестовым классам (всего, упало, успешно)
- Сортирует классы по количеству упавших тестов (по убыванию)
- Использует цвета для удобного чтения (зеленый для успеха, красный для ошибок)
- Возвращает exit code 0 если все тесты прошли, 1 если есть упавшие

Пример вывода скрипта:

```
================================================================================
✅ СБОРКА УСПЕШНА
================================================================================

Статистика тестов:
Всего тестов: 145
✅ Успешные: 142
❌ Упавшие: 3
❌ Список упавших тестов:
  - MainScreenViewModelTest::addItem_withValidData_addsItem
  - DetailScreenViewModelTest::calculateDays_whenSameDate_returnsZero
  - ItemRepositoryImplTest::getAllItems_whenEmpty_returnsEmptyList

================================================================================
Статистика по тестовым классам (5 классов):
================================================================================
Класс                                          Упало  Успешно  Всего
----------------------------------------------------------------------------
MainScreenViewModelTest                             1      12       13
DetailScreenViewModelTest                          1      8        9
ItemRepositoryImplTest                            1      15       16
================================================================================
```

### Другие команды тестирования

- `make android-test` - запуск интеграционных тестов на Android устройстве
- `make test-all` - запуск всех тестов (unit + интеграционные)
- `make android-test-report` - открыть HTML отчет интеграционных тестов в браузере

## Структура

- `app/src/test/` - unit-тесты (ViewModels, Use Cases, Domain models)
- `app/src/androidTest/` - integration/UI тесты (DAO, Repository, UI компоненты)
- Структура зеркалит код
- Имена классов: `*Test`

## Best Practices

**Важно:** Ограничения на интеграционные тесты с ViewModels

- ❌ **Запрещено:** Создавать новые интеграционные тесты с ViewModels
- ⚠️ **Допустимо:** Для существующих интеграционных тестов ViewModels использовать `runTest`, `MainDispatcherRule` и `Turbine`
- ✅ **Рекомендуется:** Тестировать ViewModels только через unit-тесты с MockK
- ✅ **Допустимо:** Тестировать DAO и Repository через интеграционные тесты без ViewModels
- ✅ **Допустимо:** UI-тесты для Compose компонентов без бизнес-логики

**Причина ограничений:**

- Конфликт между `runBlocking` и `viewModelScope.launch`
- Flow репозитория не активируется корректно в тестах
- Тесты зависают бесконечно или падают
- Unit-тесты с MockK обеспечивают лучшее покрытие бизнес-логики
- В JetpackDays часть интеграционных тестов ViewModels была отключена (@Ignore) из-за сложностей с асинхронностью

**Отличия от Jetpack-WorkoutApp:**

В Jetpack-WorkoutApp интеграционные тесты ViewModels возможны (но не рекомендуются), так как там используется сетевой слой с API. В JetpackDays работает полностью офлайн без сетевых запросов, поэтому unit-тесты с моками полностью покрывают бизнес-логику и нет необходимости в сложных интеграционных тестах ViewModels.

### Рабочий подход к тестированию

См. подробные примеры в [references/EXAMPLES.md](references/EXAMPLES.md).

#### Unit-тесты ViewModels (с MockK)

#### Интеграционные тесты DAO и Repository

#### Интеграционные тесты ViewModels (только для существующих)

#### UI-тесты Compose компонентов

### Тестирование Flow с исключениями

**Важно:** Для Flow с исключениями, которые обрабатываются через `catch`, используйте `first()` или `collect()` вместо Turbine.

См. примеры в [references/EXAMPLES.md](references/EXAMPLES.md):

#### Тестирование IOException (обрабатывается в catch)

#### Тестирование других исключений (пробрасываются дальше)

#### Мокирование Android Log

### Общие практики

- Быстрые и независимые тесты
- Описательные имена
- Один тест - одна проверка
- Тестировать поведение, не реализацию
- Интеграционные тесты только для DAO и Repository
- Unit-тесты для ViewModels с моками
- UI-тесты для Compose компонентов без бизнес-логики
- Использовать JUnit 5 аннотации (`@Test`, `@Before`, `@After`)
- Использовать assertions JUnit 5 (`assertEquals`, `assertTrue`, `assertNull`)
