Покрытие кода тестами в Kotlin: от 0 до 80% за вечер

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Вступление: Почему «зелёные» тесты — это иллюзия?

Вы запускаете тесты, видите заветную зелёную полосу и чувствуете себя супергероем. Но что, если я скажу, что ваши тесты могут быть бесполезны? Что они проверяют только одну строчку из десяти, а остальные девять — это «мёртвая зона», где живут баги? Именно здесь на сцену выходит покрытие кода тестами (code coverage). Это не просто цифры для отчёта, а ваш личный радар, показывающий, насколько хорошо тесты «покрывают» ваш Kotlin-код.

В этой статье мы разберём, как превратить покрытие кода в Kotlin из скучной метрики в мощный инструмент. Вы узнаете, какие инструменты реально работают, что означают проценты и как не загнать себя в ловушку «100% покрытия». Поехали!

Что такое покрытие кода и почему это важно для Kotlin?

Представьте, что ваш код — это поле для гольфа, а тесты — игроки. Покрытие кода — это карта, показывающая, по каким участкам поля прошли игроки. Если игроки обходят стороной опасный бункер (сложный алгоритм), вы об этом узнаете.

В мире Kotlin, где мы часто используем корутины, sealed классы и extension-функции, наивные тесты могут легко пропустить целые ветки логики. Метрики покрытия помогают ответить на три вопроса:

  • Какой процент строк кода был выполнен во время тестов?
  • Какие условия (if/else, when) были проверены?
  • Какие функции остались без внимания?

Без этих метрик вы рискуете выпустить в прод код, который работает «только по понедельникам».

Инструменты для измерения покрытия в Kotlin: Топ-3

В экосистеме Kotlin есть три главных героя, которые помогут вам измерить code coverage. Давайте познакомимся с каждым.

1. JaCoCo (Java Code Coverage) — Ветеран

Самый популярный инструмент. Работает на уровне байт-кода, поэтому подходит и для Java, и для Kotlin. Его главный плюс — богатая статистика и интеграция с CI/CD.

Как подключить в Gradle (Kotlin DSL):

plugins {
    id("jacoco")
}

tasks.jacocoTestReport {
    dependsOn(tasks.test) // Запускаем тесты перед отчётом
    reports {
        xml.required.set(true)
        html.required.set(true) // Генерируем красивый HTML-отчёт
    }
}

После запуска ./gradlew jacocoTestReport вы получите HTML-файл, где красным подсвечены непокрытые строки. Удобно, правда?

2. Kover — Современный игрок

Специально разработан для Kotlin. В отличие от JaCoCo, Kover работает на уровне AST (абстрактного синтаксического дерева), что делает его более точным для Kotlin-специфичных конструкций (например, inline-функций).

Подключение Kover:

plugins {
    id("org.jetbrains.kotlinx.kover") version "0.7.3"
}

kover {
    excludeSourceSets {
        names("test") // Исключаем сами тесты из отчёта
    }
}

Kover генерирует отчёты в форматах HTML, XML и даже в виде бейджей для GitHub. Это идеальный выбор для чистого Kotlin-проекта.

3. IntelliJ IDEA Coverage — Для быстрой проверки

Встроенный инструмент. Запускаете тесты с покрытием (зелёный значок «Run with Coverage») и видите раскрашенный код прямо в IDE. Удобно для локальной разработки, но не для CI.

Совет: Используйте IntelliJ для быстрой итерации, а JaCoCo или Kover — для финального отчёта.

Метрики покрытия кода: Что означают эти проценты?

Когда вы видите «80% покрытия», это звучит круто. Но 80% чего? Давайте разберём основные метрики, которые выдают все инструменты.

Line Coverage (Покрытие строк)

Самая простая метрика. Сколько строк исходного кода было затронуто тестами. Если строка не выполнилась ни разу — она считается непокрытой.

Пример:

fun process(value: Int): String {
    if (value > 0) {       // Строка 1
        return "Positive"  // Строка 2
    } else {
        return "Negative"  // Строка 3
    }
}

Если вы протестировали только value > 0, то строка 3 останется красной. Line coverage покажет 66%.

Branch Coverage (Покрытие ветвей)

Более умная метрика. Она проверяет, были ли пройдены все возможные пути выполнения (true/false для if, каждый case для when).

В примере выше branch coverage будет 50%, так как проверена только одна ветка (true), а другая (false) — нет.

Method Coverage (Покрытие методов)

Процент функций, которые были вызваны хотя бы один раз. Полезно для поиска «мёртвого кода».

Примеры реального кода на Kotlin: Играем с покрытием

Давайте напишем простой класс и посмотрим, как ведут себя метрики.

Код, который нужно протестировать

class Calculator {

    fun calculate(a: Int, b: Int, operation: String): Int {
        return when (operation) {
            "add" -> a + b
            "subtract" -> a - b
            else -> throw IllegalArgumentException("Unknown operation")
        }
    }

    fun isEven(number: Int): Boolean {
        return number % 2 == 0
    }
}

Плохой тест (низкое покрытие)

class CalculatorTest {

    @Test
    fun `test add`() {
        val calc = Calculator()
        assertEquals(5, calc.calculate(2, 3, "add"))
    }
}

Результат: Line coverage ~30%. Мы не проверили subtract, else и isEven. Branch coverage по when — 33% (проверен только один case).

Хороший тест (высокое покрытие)

class CalculatorTest {

    @Test
    fun `test add`() {
        val calc = Calculator()
        assertEquals(5, calc.calculate(2, 3, "add"))
    }

    @Test
    fun `test subtract`() {
        val calc = Calculator()
        assertEquals(1, calc.calculate(3, 2, "subtract"))
    }

    @Test(expected = IllegalArgumentException::class)
    fun `test unknown operation`() {
        val calc = Calculator()
        calc.calculate(1, 1, "multiply")
    }

    @Test
    fun `test isEven`() {
        val calc = Calculator()
        assertTrue(calc.isEven(4))
        assertFalse(calc.isEven(3))
    }
}

Результат: Line coverage

Блоги

Рекомендаций по книгам