Покрытие unit тестами всех классов модуля где есть смысл у тестов
Generates comprehensive unit tests for all testable classes in a module with parallel batch processing.
/plugin marketplace add IvanLutsenko/awac-claude-code-plugins/plugin install bereke-business-test-gen@awac-claude-code-pluginspath/to/moduleСоздать unit тесты для ВСЕХ классов модуля, где unit тесты имеют смысл.
Включает:
Исключает:
Быстрая шпаргалка (начни с этого - 50 строк):
!cat ~/.claude/plugins/marketplaces/awac-claude-code-plugins/plugins/bereke-business-test-gen/standards/android-kotlin-quick-ref.md
Полное руководство (если нужны детали - 600+ строк):
!cat ~/.claude/plugins/marketplaces/awac-claude-code-plugins/plugins/bereke-business-test-gen/standards/android-kotlin.md
Найди ВСЕ Kotlin файлы в модуле:
find {module_path}/src/main -type f -name "*.kt" | grep -v "/di/" | grep -v "/models/"
Для каждого найденного файла:
Категория A - Требуют тестов (высокий приоритет):
Категория B - Требуют тестов (средний приоритет):
Категория C - НЕ требуют тестов:
Класс требует тестов если:
Класс НЕ требует тестов если:
Не тестируй весь класс сразу! Проверь каждый public метод:
Перед созданием теста спрашивай:
1. Возвращает ли метод значение?
✅ fun validate(phone: String): Boolean → ТЕСТИРОВАТЬ
❌ fun logEvent(name: String) → ПРОПУСТИТЬ (void)
2. Может ли быть замокирована зависимость?
✅ api.getStatus() внутри метода → ТЕСТИРОВАТЬ
❌ Firebase.track() SDK вызов → ПРОПУСТИТЬ
3. Есть ли логика для асёрта?
✅ if (x > 0) return true → ТЕСТИРОВАТЬ
❌ просто receiver.call() → ПРОПУСТИТЬ
ВСЕ ДА? → создавай тест
ХОТЯ БЫ ОДИН НЕТ? → пропусти метод (нечего проверять)
Типичный класс:
class PaymentValidator {
// ✅ ТЕСТИРУЕМ - возвращает значение, логика
fun isCardValid(card: String): Boolean {
return card.length == 16 && card.all { it.isDigit() }
}
// ❌ ПРОПУСКАЕМ - void, нет return
fun reportCardUsage(card: String) {
analytics.track("card_used")
}
}
Требует теста:
// Validator с логикой
class PhoneValidator {
fun validate(phone: String): Boolean {
if (phone.isEmpty()) return false
val cleanPhone = phone.filter { it.isDigit() }
return cleanPhone.length in 10..11
}
}
// Formatter с логикой
class CurrencyFormatter {
fun format(amount: Double): String {
return when {
amount >= 1_000_000 -> "${amount / 1_000_000}M"
amount >= 1_000 -> "${amount / 1_000}K"
else -> amount.toString()
}
}
}
// Util с логикой
class DateUtils {
fun isExpired(timestamp: Long): Boolean {
val now = System.currentTimeMillis()
return timestamp < now - EXPIRY_DURATION
}
}
НЕ требует теста:
// Простой data class
data class User(val id: String, val name: String)
// DI module
val appModule = module {
single { AuthRepository(get()) }
}
// Простой mapper
fun UserDTO.toModel() = User(
id = this.id,
name = this.name
)
// Константы
object Constants {
const val API_URL = "https://api.example.com"
}
Создай план в TodoWrite, сгруппированный по приоритетам:
=== Категория A: Бизнес-логика (8 классов) ===
1. AuthViewModel - нужен BaseTest + 3 файла
2. LoginUseCase - нужен тест
3. AuthInteractor - нужен тест
4. AuthRepository - уже покрыт ✅
...
=== Категория B: Utils и helpers (5 классов) ===
9. PhoneValidator - нужен тест
10. AmountFormatter - нужен тест
11. SessionCache - нужен тест
...
=== Категория C: Не требуют тестов (12 классов) ===
- UserDTO (data class)
- AppModule (DI)
- UserMapper (простой)
...
Итого к покрытию: 13 классов
ПАРАЛЛЕЛЬНАЯ ГЕНЕРАЦИЯ ПО БАТЧАМ для ускорения:
classes_to_test = [... список из TodoWrite категорий A и B ...]
batch_size = 3-5 классов # Оптимальный размер батча
batches = split(classes_to_test, batch_size)
# Пример: 15 классов → 3 батча по 5 классов
# Batch 1: [AuthViewModel, LoginUseCase, AuthInteractor, AuthRepository, UserValidator]
# Batch 2: [SessionCache, PhoneValidator, AmountFormatter, DateUtils, CryptoHelper]
# Batch 3: [PaymentInteractor, DocumentRepository, BiometricHelper, TokenManager, OtpValidator]
Для каждого батча:
# ПАРАЛЛЕЛЬНО через Task tool (SINGLE MESSAGE, MULTIPLE CALLS!)
for class_file in batch:
Task(
subagent_type="test-engineer",
model="sonnet",
prompt="Generate test for {class_file} with full coverage loop.
Use template matching for simple methods.
Target: 80%+ coverage, 3.0+ quality score",
run_in_background=false # Параллельно в одном сообщении!
)
# Claude Code выполнит все Task calls в батче ПАРАЛЛЕЛЬНО!
Example:
# Single message with 5 parallel Task calls:
Task(subagent_type="test-engineer", prompt="Generate test for AuthViewModel...")
Task(subagent_type="test-engineer", prompt="Generate test for LoginUseCase...")
Task(subagent_type="test-engineer", prompt="Generate test for AuthInteractor...")
Task(subagent_type="test-engineer", prompt="Generate test for AuthRepository...")
Task(subagent_type="test-engineer", prompt="Generate test for UserValidator...")
# Запусти тесты для всего модуля (ONE TIME per batch)
./gradlew :{module}:testDebugUnitTest
LOOP через все батчи:
1. Запусти параллельную генерацию (5 test-engineer agents)
2. Дождись завершения всех agents
3. Компилируй модуль один раз
4. Переходи к следующему батчу
Performance Gains:
Notes:
Генерируй в порядке:
1. Категория A (критичная бизнес-логика):
2. Категория B (вспомогательная логика):
ОБЯЗАТЕЛЬНО соблюдай при создании всех примеров:
mock для всех моковValidator:
class PhoneValidatorTest {
private lateinit var validator: PhoneValidator
@BeforeEach
fun setUp() {
validator = PhoneValidator()
}
@DisplayName("When phone is valid 10 digits - Then returns true")
@Test
fun phoneValid10Digits_returnsTrue() {
// Given
val phone = "1234567890"
// When
val result = validator.validate(phone)
// Then
assertThat(result).isTrue()
}
@DisplayName("When phone is empty - Then returns false")
@Test
fun phoneEmpty_returnsFalse() {
// Given
val phone = ""
// When
val result = validator.validate(phone)
// Then
assertThat(result).isFalse()
}
@DisplayName("When phone has formatting characters - Then validates cleaned number")
@Test
fun phoneWithFormatting_validatesCleanedNumber() {
// Given
val phone = "+7 (123) 456-78-90"
// When
val result = validator.validate(phone)
// Then
assertThat(result).isTrue()
}
}
Formatter:
class CurrencyFormatterTest {
private lateinit var formatter: CurrencyFormatter
@BeforeEach
fun setUp() {
formatter = CurrencyFormatter()
}
@DisplayName("When amount is millions - Then formats with M suffix")
@Test
fun amountMillions_formatsWithM() {
// Given
val amount = 2_500_000.0
// When
val result = formatter.format(amount)
// Then
assertThat(result).isEqualTo("2.5M")
}
@DisplayName("When amount is thousands - Then formats with K suffix")
@Test
fun amountThousands_formatsWithK() {
// Given
val amount = 5_500.0
// When
val result = formatter.format(amount)
// Then
assertThat(result).isEqualTo("5.5K")
}
}
Cache:
@ExperimentalCoroutinesApi
class SessionCacheTest {
private lateinit var cache: SessionCache
@BeforeEach
fun setUp() {
cache = SessionCache()
}
@AfterEach
fun tearDown() {
FlowTestUtils.cleanupFlowResources()
}
@DisplayName("WHEN data is set THEN cache emits new value")
@Test
fun dataSet_cacheEmitsNewValue() = runTest {
// Given
val expected = SessionData(userId = "123")
// When
cache.setSession(expected)
// Then
cache.sessionFlow.test {
val item = awaitItem()
assertThat(item).isEqualTo(expected)
cancelAndIgnoreRemainingEvents()
}
}
}
## ✅ Полное покрытие модуля: {MODULE_NAME}
### Статистика
**Всего Kotlin файлов:** 35
**Категория A - Бизнес-логика:** 8 классов
- До: 3 покрыто (38%)
- После: 8 покрыто (100%) ✅
**Категория B - Utils и helpers:** 5 классов
- До: 0 покрыто (0%)
- После: 5 покрыто (100%) ✅
**Категория C - Не требуют тестов:** 22 класса
- Data classes: 8
- DI modules: 3
- UI components: 7
- Simple mappers: 4
**Итого покрытие:** 13/13 классов (100%) ✅
### Созданные тесты
#### Бизнес-логика
1. ✅ AuthViewModelBaseTest + 3 файла
2. ✅ LoginUseCaseTest
3. ✅ LogoutUseCaseTest
4. ✅ AuthInteractorTest
5. ✅ AuthRepositoryImplTest
#### Validators & Formatters
6. ✅ PhoneValidatorTest
7. ✅ AmountFormatterTest
8. ✅ DateFormatterTest
#### Cache & State
9. ✅ SessionCacheTest
10. ✅ AuthStateMachineTest
#### Utils
11. ✅ DateUtilsTest
12. ✅ StringUtilsTest
13. ✅ CryptoHelperTest
### Классы не требующие тестов
**Data classes (8):**
- UserDTO, AuthResponseDTO, SessionData, ...
**DI modules (3):**
- AppModule, NetworkModule, ...
**UI components (7):**
- LoginScreen.kt, AuthActivity.kt, ...
**Simple mappers (4):**
- UserMapper.kt, AuthMapper.kt, ...
### Рекомендации
1. Запустить все тесты: `./gradlew :{module}:test`
2. Проверить покрытие: `./gradlew :{module}:koverHtmlReport`
3. Целевое покрытие: 80%+ для бизнес-логики
# Покрыть весь модуль
/test-module-all feature/auth
# Покрыть impl модуль
/test-module-all feature/qr-signing/qr-signing-impl
При генерации ВСЕХ тестов соблюдай:
После завершения генерации всех тестов:
# 1. Проверка синтаксиса
./gradlew :{module}:compileDebugUnitTestKotlin
# 2. Проверка линтера и удаление неиспользуемых импортов
./gradlew :{module}:lintDebugUnitTest
# 3. Запуск всех тестов
./gradlew :{module}:testDebugUnitTest
# 4. Проверка покрытия кода
./gradlew :{module}:koverVerify
./gradlew :{module}:koverHtmlReport # отчет в build/reports/kover/html/
Если лinter нашел unused imports:
Если тесты не компилируются:
Целевое покрытие: >= 70% (стремись к 100%)
После успешного завершения всех тестов ОБЯЗАТЕЛЬНО выведи итоговое покрытие модуля:
# Извлекаем общее покрытие из XML отчета
COVERAGE_FILE="build/reports/kover/report.xml"
# Получаем LINE и INSTRUCTION покрытие модуля
echo "📊 Покрытие модуля {MODULE_NAME}:"
grep -E 'counter type="(LINE|INSTRUCTION)"' "$COVERAGE_FILE" | head -2
Выведи в формате:
📊 Итоговое покрытие модуля {MODULE_NAME}:
- LINE coverage: XX.X%
- INSTRUCTION coverage: XX.X%
✅ Статус: Покрытие [на уровне/ниже] целевого (70%)
Интерпретация результатов:
Если хочешь достичь 100% покрытия - используй этот workflow для итеративной генерации:
# Создай первый набор тестов обычным способом
/test-module-all feature/auth
# Запусти Kover с XML отчетом
./gradlew :{module}:koverXmlReport
# XML будет в: build/reports/kover/report.xml
# Показать методы где missed > 0:
grep -E '<method.*missed="[1-9]"' build/reports/kover/report.xml | grep -oP 'name="\K[^"]*'
# Или все методы с их покрытием:
grep '<method' build/reports/kover/report.xml | grep -oP 'name="\K[^"]*' | head -20
Вывод покажет методы типа:
getFcmToken
postMessageStatus
deleteToken
onIntentReceived
# Для каждого метода найденного в Шаге 3:
/generate-test feature/auth/data/repositories/AuthRepository.kt
# Сосредоточься на методе `getFcmToken`:
# - Happy path: успешное получение токена
# - Error case: токен недоступен
# - Edge case: null/empty результат
# Перегенерируй отчет
./gradlew :{module}:koverXmlReport
# Проверь новый результат
grep -c '<method' build/reports/kover/report.xml # всего методов
grep -c 'missed="0"' build/reports/kover/report.xml # полностью покрыто
# Показать итоговое покрытие
./gradlew :{module}:koverVerify
# Если не прошло (< 100%):
# 1. Повтори Шаг 3 (найди новые uncovered методы)
# 2. Повтори Шаг 4 (генерируй тесты)
# 3. Повтори Шаг 5 (проверь результаты)
# Когда пройдет - готово! ✅
# 1. Начальная генерация
/test-module-all feature/auth
# 2. Первая итерация
./gradlew :feature:auth:koverXmlReport
grep '<method' build/reports/kover/report.xml | wc -l # => 15 методов
grep -E 'missed="[1-9]"' build/reports/kover/report.xml | wc -l # => 6 методов не покрыто
# 3. Генерируем тесты для 6 методов (Шаг 4)
/generate-test feature/auth/data/AuthRepository.kt # getFcmToken
/generate-test feature/auth/data/AuthRepository.kt # postMessageStatus
# ... еще 4 метода
# 4. Вторая итерация
./gradlew :feature:auth:koverXmlReport
grep -E 'missed="[1-9]"' build/reports/kover/report.xml | wc -l # => 2 метода
# 5. Генерируем тесты для оставшихся 2
/generate-test feature/auth/domain/AuthInteractor.kt # método_x
/generate-test feature/auth/domain/AuthUseCase.kt # método_y
# 6. Проверка финального результата
./gradlew :feature:auth:koverVerify # ✅ BUILD SUCCESSFUL
Если надоел ручной процесс - создай скрипт:
#!/bin/bash
MODULE="feature:auth"
COVERAGE_TARGET=100
while true; do
# 1. Генерируй отчет
./gradlew :${MODULE}:koverXmlReport
# 2. Найди uncovered методы
UNCOVERED=$(grep -E 'missed="[1-9]"' build/reports/kover/report.xml | \
grep -oP 'name="\K[^"]*' | sort -u)
# 3. Если нет uncovered - выход
if [ -z "$UNCOVERED" ]; then
echo "✅ 100% coverage achieved!"
break
fi
# 4. Для каждого uncovered метода - даю инструкцию генерировать тест
echo "❌ Found uncovered methods: $UNCOVERED"
echo "📝 Generate tests for these methods:"
echo "$UNCOVERED" | head -5 # Show max 5 для не затопить user
# 5. User должен запустить /generate-test для каждого
# (полная автоматизация требует интеграции AI model внутри скрипта)
echo "Run: /generate-test {class-with-these-methods}"
break
done
Kover парсинг: XML структура может отличаться по версиям
<method name="methodName">
<counter type="LINE" missed="0" covered="5"/> ← это важно
</method>
Missed vs Covered:
missed="0" = полностью покрыто ✅missed="1" = 1 инструкция не покрыта ❌LINE vs INSTRUCTION:
Повторяющийся процесс: