Coverage thresholds и enforcement
Pull request с новым кодом. Coverage упал с 85% до 70%. Код мержится. Через месяц — 50%. Как автоматически блокировать такое?
Цель: Настроить минимальные пороги покрытия и enforcement в CI.
Вы точно готовы?
Убедитесь, что умеете:
# Измерять coverage
pytest --cov=src --cov-report=html
# Читать метрики
# Name Stmts Miss Cover
# mymodule.py 50 10 80%Если coverage непонятен — вернитесь к pytest-junior урок 4.
Проблема: постепенное снижение покрытия
Без enforcement
# Week 1
pytest --cov=src
# Coverage: 85%
# Week 2 (добавлен код без тестов)
pytest --cov=src
# Coverage: 78%
# Week 4
pytest --cov=src
# Coverage: 65%
# Week 8
pytest --cov=src
# Coverage: 45% ❌Проблема: Покрытие падает незаметно, никто не замечает!
Решение: --cov-fail-under
Минимальный порог
pytest --cov=src --cov-fail-under=80Если coverage < 80%:
---------- coverage: platform darwin, python 3.11 -----------
Name Stmts Miss Cover
---------------------------------
src/app.py 50 15 70%
---------------------------------
TOTAL 50 15 70%
FAIL Required test coverage of 80% not reached. Total coverage: 70.00%❌ pytest завершается с exit code 1 — CI заблокирован!
Настройка в pytest.ini
# pytest.ini
[pytest]
addopts =
--cov=src
--cov-report=term-missing
--cov-report=html
--cov-fail-under=80 # Минимум 80%Теперь просто:
pytest✅ Всегда проверяет порог!
Разные пороги для разных команд
# pytest.ini (для локальной разработки)
[pytest]
addopts =
--cov=src
--cov-fail-under=70 # Мягкий порог локально# .gitlab-ci.yml (для CI)
test:
script:
- pytest --cov-fail-under=80 # Строгий порог в CIPer-module coverage
Проблема: общий coverage скрывает проблемы
Name Stmts Miss Cover
--------------------------------------------
src/core/auth.py 50 0 100% ✅
src/core/payments.py 50 40 20% ❌
--------------------------------------------
TOTAL 100 40 60% ⚠️60% общего покрытия выглядит приемлемо, но payments — катастрофа!
.coveragerc: минимумы по модулям
# .coveragerc
[run]
source = src
omit =
*/tests/*
*/migrations/*
[report]
precision = 2
show_missing = True
# Минимальное покрытие для разных модулей
fail_under = 80
[coverage:paths]
source =
src/
# Per-module thresholds (через плагин)Плагин для per-module:
pip install coverage-conditional-plugin# .coveragerc
[coverage:run]
plugins = coverage_conditional_plugin
[coverage:paths:conditional]
# Критичные модули — 90%
src/payments/ = 90
src/auth/ = 90
# Обычные модули — 80%
src/api/ = 80
# UI/CLI — 60%
src/cli/ = 60Интеграция в CI
GitLab CI
# .gitlab-ci.yml
test:
stage: test
script:
- pip install -r requirements.txt
- pytest --cov=src --cov-fail-under=80 --cov-report=xml
- coverage report --fail-under=80
coverage: '/TOTAL.*\s+(\d+%)$/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xmlGitHub Actions
# .github/workflows/test.yml
- name: Run tests with coverage
run: |
pytest --cov=src --cov-fail-under=80 --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
fail_ci_if_error: trueQuality gates с Codecov
# codecov.yml
coverage:
status:
project:
default:
target: 80% # Минимум для всего проекта
threshold: 1% # Допустимое падение
patch:
default:
target: 90% # Новый код должен быть лучше!Результат: PR блокируется если:
- Общее покрытие < 80%
- Новый код покрыт < 90%
- Coverage упал > 1%
Практический workflow (bonus)
Локальная разработка
# Быстрая проверка (без строгого порога)
pytest --cov=src
# Перед commit (строгий порог)
pytest --cov=src --cov-fail-under=75Pre-commit hook
# .git/hooks/pre-commit
#!/bin/bash
pytest --cov=src --cov-fail-under=75 --cov-report=term-missing
if [ $? -ne 0 ]; then
echo "❌ Coverage is below 75%, commit blocked!"
exit 1
fi
echo "✅ Coverage check passed"CI pipeline
stages:
- test
- quality-gate
- deploy
unit-tests:
stage: test
script:
- pytest --cov=src --cov-fail-under=70
coverage-check:
stage: quality-gate
script:
- pytest --cov=src --cov-fail-under=80
- coverage html
artifacts:
paths:
- htmlcov/
deploy:
stage: deploy
dependencies:
- coverage-check # Не задеплоим если coverage < 80%Что вы изучили
- --cov-fail-under — минимальный порог покрытия
- pytest.ini — автоматическая проверка
- Per-module — разные пороги для разных модулей
- CI integration — блокировка плохих PR
- Quality gates — Codecov, SonarQube
- Workflow — локально vs CI vs pre-commit
Следующий урок
Поздравляю! Вы изучили 7 из 8 уроков pytest-professional-tools!
Теперь пора собрать всё вместе и превратить учебный проект в production-ready!
Переходите к уроку 7: Production-ready проект
В следующем уроке вы:
- Применяете всё изученное на практике
- Создаёте production-ready структуру
- Настраиваете xdist, coverage, плагины
- Готовите проект к команде
Устранение неисправностей
Проверьте exit code команды.
pytest --cov=src --cov-fail-under=80
echo $? # Должен быть 1 если coverage < 80%В CI нельзя глушить ошибку:
# ✅ ПРАВИЛЬНО
script:
- pytest --cov-fail-under=80
# ❌ НЕПРАВИЛЬНО (игнорирует exit code)
script:
- pytest --cov-fail-under=80 || true
Coverage — не единственный критерий.
- Добавьте mutation testing (mutpy/pytest-mutagen)
- Проводите code review по сценарию
- Запускайте статанализ (mypy, ruff/flake8)
Проверяйте покрытие дельты, а не только общее.
coverage xml
diff-cover coverage.xml --fail-under=90 # сравнение с main
Поднимайте порог поэтапно.
# Week 1
--cov-fail-under=50
# Week 4
--cov-fail-under=60
# Week 8
--cov-fail-under=70
# Week 12
--cov-fail-under=80