Pitest
Pitest — это самый серьёзный инструмент из «пирамиды»: он объективно отвечает на вопрос «а тесты вообще кусаются, или просто зелёные». Расскажу по делу, включая честные Kotlin-подводные камни (это ключевое
для вашего стека).
- Идея: мутационное тестирование
Обычное покрытие (jacoco) считает «какие строки исполнились». Pitest считает другое — «какие поломки кода тесты ловят».
Механика:
- Pitest берёт твой байткод и вносит маленькую поломку — мутацию. Например >= → >, + → -, return x → return null, убирает вызов метода, инвертирует if.
- Прогоняет тесты против этого «мутанта».
- Итог по каждому мутанту:
- KILLED — хотя бы один тест упал. 👍 тесты поймали поломку.
- SURVIVED — все тесты зелёные, хотя код сломан. 👎 дыра в ассертах — ровно твой «зелёный, но слабый».
- NO_COVERAGE — мутанта даже не исполнил ни один тест (пробел покрытия).
- TIMED_OUT / NON_VIABLE — мутация зациклила/не скомпилировалась (шум).
Mutation score = KILLED / всего мутантов. Это метрика силы тестов, а не факта исполнения.
- Почему это бьёт точнее jacoco
Классический пример на вашем коде — isDoneWell:
repetitionIndex >= minRepetitionIndex && rightAnswersIndex >= minRightAnswersIndex
- jacoco: если хоть один тест сюда зашёл — строка «покрыта», 100%.
- Pitest: сгенерит мутантов >=→>, &&→||, >=→<… Если у тебя нет теста ровно на граничное значение == 0.8 или на «один индекс прошёл, другой нет» — эти мутанты SURVIVED. Ты сразу видишь: пороговая логика тестами не
зафиксирована, хотя покрытие «зелёное 100%».
Как бы встроилось в наш Gradle Kotlin DSL
Плагин info.solidsoft.pitest (gradle-pitest-plugin), с JUnit5-мостом:
plugins {
id("info.solidsoft.pitest") version "1.15.0" // проверить актуальную
}
dependencies {
// мост для JUnit 5 (у вас JUnit Platform)
pitest("org.pitest:pitest-junit5-plugin:1.2.1")
}
pitest {
junit5PluginVersion.set("1.2.1")
// не весь проект! только ценное ядро:
targetClasses.set(listOf("com.epam.brn.service."))
targetTests.set(listOf("com.epam.brn.service."))
threads.set(Runtime.getRuntime().availableProcessors())
outputFormats.set(listOf("HTML", "XML"))
timestampedReports.set(false)
// на старте не заваливать build — без порога:
// mutationThreshold.set(60)
}
Запуск: gradlew pitest → HTML-отчёт в build/reports/pitest/ с построчной подсветкой survived-мутантов.
Pitest
Pitest — это самый серьёзный инструмент из «пирамиды»: он объективно отвечает на вопрос «а тесты вообще кусаются, или просто зелёные». Расскажу по делу, включая честные Kotlin-подводные камни (это ключевое
для вашего стека).
Обычное покрытие (jacoco) считает «какие строки исполнились». Pitest считает другое — «какие поломки кода тесты ловят».
Механика:
- KILLED — хотя бы один тест упал. 👍 тесты поймали поломку.
- SURVIVED — все тесты зелёные, хотя код сломан. 👎 дыра в ассертах — ровно твой «зелёный, но слабый».
- NO_COVERAGE — мутанта даже не исполнил ни один тест (пробел покрытия).
- TIMED_OUT / NON_VIABLE — мутация зациклила/не скомпилировалась (шум).
Mutation score = KILLED / всего мутантов. Это метрика силы тестов, а не факта исполнения.
Классический пример на вашем коде — isDoneWell:
repetitionIndex >= minRepetitionIndex && rightAnswersIndex >= minRightAnswersIndex
зафиксирована, хотя покрытие «зелёное 100%».
Как бы встроилось в наш Gradle Kotlin DSL
Плагин info.solidsoft.pitest (gradle-pitest-plugin), с JUnit5-мостом:
plugins {
id("info.solidsoft.pitest") version "1.15.0" // проверить актуальную
}
dependencies {
// мост для JUnit 5 (у вас JUnit Platform)
pitest("org.pitest:pitest-junit5-plugin:1.2.1")
}
pitest {
junit5PluginVersion.set("1.2.1")
// не весь проект! только ценное ядро:
targetClasses.set(listOf("com.epam.brn.service."))
targetTests.set(listOf("com.epam.brn.service."))
threads.set(Runtime.getRuntime().availableProcessors())
outputFormats.set(listOf("HTML", "XML"))
timestampedReports.set(false)
// на старте не заваливать build — без порога:
// mutationThreshold.set(60)
}
Запуск: gradlew pitest → HTML-отчёт в build/reports/pitest/ с построчной подсветкой survived-мутантов.