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

Покрытие кода тестами: что измеряет coverage.py и как это правильно понимать.

Покрытие (coverage) — доля строк (или ветвей) кода, которые выполнились хотя бы раз во время прогона тестов.

Что такое покрытие

Когда тестов становится много, возникает вопрос: а всё ли важное мы проверяем? Инструмент покрытия запускает тесты и отмечает, какие строки кода при этом выполнились, а какие — нет. Непокрытые строки — это код, который тесты ни разу не задели. Часто там и прячутся баги.

coverage.py: установка и запуск

Стандартный инструмент в Python — coverage.py (для pytest есть обёртка pytest-cov). Он сторонний, ставится через pip:

pip install coverage

# запустить тесты под наблюдением покрытия
coverage run -m unittest discover

# показать отчёт в терминале
coverage report -m

# сгенерировать наглядный HTML-отчёт
coverage html

Отчёт в терминале выглядит примерно так — видно процент и номера непокрытых строк:

Name           Stmts   Miss  Cover   Missing
----------------------------------------------
calc.py           20      2    90%   15-16
----------------------------------------------
TOTAL             20      2    90%

Столбец Missing указывает: строки 15–16 не выполнялись ни одним тестом. Это подсказка — добавить тест, который пройдёт по этой ветке.

Покрытие строк против покрытия ветвей

Простое покрытие считает строки. Но строка может выполниться, а её ветвь — нет. Рассмотрите:

def grade(score):
    if score >= 60:
        return "сдал"
    return "не сдал"

Тест только с score=80 выполнит if и первый return — строки «покрыты», но ветка return "не сдал" ни разу не сработала. Покрытие ветвей (coverage run --branch) ловит такие пропуски: оно требует пройти и по True, и по False.

Главная ловушка: 100% ≠ нет багов

Высокое покрытие не гарантирует правильность. Можно «выполнить» строку, но ничего толком не проверить:

def test_useless():
    add(2, 2)          # строка выполнилась — покрытие растёт
    # ...но нет ни одного assert — мы ничего не проверили!

Покрытие показывает, что код выполнялся, а не что он правильный. Гнаться за 100% любой ценой вредно: появляются пустые тесты ради цифры. Разумный ориентир для важной логики — 80–90% при осмысленных проверках, а не «красивый» процент.

Как пользоваться покрытием с пользой

  • Смотрите на непокрытые участки — это карта «слепых зон».
  • Приоритет — покрыть ветвления и обработку ошибок, а не геттеры.
  • Включайте --branch, чтобы видеть непройденные ветки if/else.
  • Не превращайте процент в самоцель: важно качество проверок.

Покрытие как инструмент, а не как оценка

Самая правильная установка: покрытие — это навигатор по слепым зонам, а не оценка качества вашей работы. Откройте HTML-отчёт coverage html — он подсвечивает красным строки, которые тесты не задели. Пробегитесь по ним глазами: часто среди них обработка редких ошибок, экзотические ветки if, ранние return. Именно там, в нехоженых местах, и живут баги. Покрытие подсказывает, куда смотреть, а решение «нужен ли здесь тест» принимаете вы, исходя из важности кода.

Чего покрытие не видит вовсе

Полезно знать пределы метрики. Покрытие не замечает отсутствующих случаев: если у функции есть поведение, которое вы вообще не вызвали ни одним тестом, покрытых строк может быть 100%, но целый сценарий останется непроверенным. Оно также ничего не говорит о качестве assert'ов и не ловит логические ошибки в самих тестах. Поэтому покрытие — лишь один из сигналов. Вместе с ним смотрите, проверяете ли вы граничные случаи и обработку ошибок по существу, а не только ради цифры в отчёте.

Итог

  • Покрытие — доля кода, выполненного тестами; находит непроверенные участки.
  • Инструмент в Python — coverage.py (или pytest-cov), сторонний.
  • Покрытие ветвей (--branch) строже покрытия строк.
  • 100% покрытия не равно отсутствию багов — важны осмысленные assert'ы.
Проверьте себя
1. Что измеряет покрытие (coverage)?
AСкорость тестов
BДолю строк/ветвей кода, выполнившихся во время тестов
CЧисло тестов
DРазмер файла
2. Почему 100% покрытия не гарантирует отсутствие багов?
AПокрытие всегда врёт
BСтрока может выполниться без единого осмысленного assert — код «покрыт», но не проверен
C100% недостижимо
DЭто гарантирует
3. Чем покрытие ветвей строже покрытия строк?
AНичем
BТребует пройти и по True, и по False каждого условия, а не просто выполнить строку
CСчитает только комментарии
DРаботает быстрее
Поддержать проект