AgentSkillsCN

testing

在 Android 项目中编写测试时要规范操作。无论是单元测试、集成测试还是 UI 测试,亦或是业务逻辑测试与 UI 组件测试,都可借助此技能来提升测试质量。

SKILL.md
--- frontmatter
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 если есть упавшие

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

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

Статистика тестов:
Всего тестов: 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.

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

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

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

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

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

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

См. примеры в references/EXAMPLES.md:

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

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

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

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

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