Покрытие кода тестами
Покрытие кода тестами: что измеряет 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'ы.