Перейти к содержанию
Innovialabs

Что такое покрытие кода тестами и сколько его действительно нужно

Основы4 мин

Коротко

Покрытие кода показывает, какие части кода выполнялись во время тестов, а не были ли они проверены; используйте его, чтобы находить непротестированные места, требовать покрытия нового кода в CI и держать умеренный порог вместо погони за 100%.

Что измеряет покрытие кода

Покрытие кода (code coverage) — доля кода, которая выполнилась во время прогона тестов. Инструмент инструментирует программу, запускает тесты и сообщает, какие строки, ветви и функции были достигнуты.

Главное: покрытие показывает, что выполнилось, а не что проверено. Тест, который вызывает функцию без единой проверки (assert), всё равно повышает покрытие.

Основные виды покрытия

ВидЧто считаетсяСлабое место
По строкам (statement)Каждая выполненная строкаСтрока с if a and b считается покрытой, даже если одно из условий ни разу не было ложным
По ветвям (branch)Каждый исход каждого решения: обе стороны if, каждый case, вход в цикл и его пропускНе проверяет все комбинации условий
По функциямКаждая функция вызвана хотя бы разОдин вызов почти ничего не говорит о граничных случаях

Покрытие по ветвям — самая полезная метрика на каждый день. Она ловит классическую дыру: ветку else, обработчик ошибки, ранний return, до которых тесты не доходят.

def discount(user):
    if user.is_vip:
        return 0.2
    return 0.0

Один тест с VIP-пользователем оставляет последнюю строку невыполненной и покрывает только одну из двух ветвей. Тест для обычного пользователя закрывает обе дыры.

Инструменты по экосистемам

  • Python — coverage.py. Запустите coverage run --branch -m pytest, затем coverage report или coverage html. С pytest плагин pytest-cov даёт pytest --cov=src --cov-branch.
  • JavaScript и TypeScript — Istanbul и c8. Istanbul (через nyc) инструментирует код; c8 использует покрытие, встроенное в движок V8. В Jest есть встроенный --coverage, а Vitest поддерживает провайдеры V8 и Istanbul.
  • Java и Kotlin — JaCoCo. Работает как Java-агент, подключается к Maven и Gradle, считает строки, ветви и другие счётчики.
  • Другие. В Go есть go test -cover, в .NET обычно используют Coverlet, и почти у каждого языка есть стандартный вариант.

Все они умеют выдавать отчёты в распространённых форматах — Cobertura XML или LCOV, которые понимают CI-сервисы и инструменты код-ревью.

Почему 100% покрытия вводит в заблуждение

  • Выполнение — не проверка. Тесты без осмысленных assert дают полное покрытие и ничего не ловят.
  • Оно не видит отсутствующий код. Покрытие измеряет написанное. У забытой валидации или необработанного случая нет строк, которые можно покрыть.
  • Комбинации ускользают. Можно покрыть каждую строку и ветвь, а конкретное сочетание входных данных всё равно упадёт.
  • Оно искажает приоритеты. Команда начинает тестировать геттеры, сгенерированный код и тривиальные обёртки ради цифры, а сложная логика получает поверхностные тесты.
  • Стоимость резко растёт в конце. Последние проценты — обычно пути ошибок, которые трудно вызвать и которые иногда лучше проверять на другом уровне.

Высокое покрытие — нормальный побочный эффект хороших тестов. Как самоцель оно поощряет неправильное поведение.

Разумные пороги

Универсального числа нет. Вместо одной цифры на весь проект думайте слоями:

  • Умеренный общий порог, ниже которого сборка не должна опускаться, выбранный исходя из текущего состояния проекта. Частая отправная точка — где-то в районе 70–80% с поправкой на проект.
  • Более высокие ожидания для критичного кода: платежи, права доступа, миграции данных, бизнес-правила.
  • Более низкие — для связующего кода: конфигурация, простые адаптеры, вёрстка UI.
  • Исключите то, что не должно учитываться: сгенерированные файлы, миграции, вспомогательный код тестов. Исключения должны быть явными и проходить ревью.
  • Поднимайте постепенно. Повышайте порог по мере роста покрытия, а не ставьте сразу большую цель.

Покрытие в CI

  1. Запускайте тесты с покрытием на каждый pull request и сохраняйте отчёт как артефакт сборки.
  2. Роняйте сборку ниже порога. В coverage.py есть --fail-under, в Jest — coverageThreshold, в c8 — --check-coverage, в JaCoCo — цель check с правилами.
  3. Проверяйте покрытие изменённого кода. Самое практичное правило: новые и изменённые строки должны быть покрыты. Инструменты вроде diff-cover или интеграции с код-ревью показывают это прямо в pull request.
  4. Смотрите на непокрытые строки, а не на процент. Настоящая ценность отчёта — указать на ветви, которые никто не проверил.
  5. Сочетайте с другими сигналами. Мутационное тестирование, код-ревью и ошибки в продакшене показывают, ловят ли тесты баги на самом деле.
# pyproject.toml
[tool.coverage.run]
branch = true
source = ["src"]

[tool.coverage.report]
fail_under = 75
show_missing = true

Частые вопросы

Оно строже и полезнее. Оно показывает непроверенные пути решений, которые покрытие по строкам скрывает, и большинство инструментов включают его одним флагом.

Поможем с этим

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

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.