Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем нужен Code Coverage
Code Coverage (покрытие кода) — это метрика, которая показывает, какой процент исходного кода протестирован автоматическими тестами. Это критически важный инструмент для обеспечения качества и надёжности приложения.
Основное определение
Coverage показывает, какие строки, ветви и пути выполнения кода были запущены во время прохождения тестов. Если покрытие 80%, это означает, что тесты проходят через 80% кода, и 20% остаётся неотестированным.
Зачем это нужно
1. Выявление слепых пятен в коде
Покрытие помогает найти код, который вообще не протестирован:
def process_payment(amount, payment_method):
if amount <= 0:
raise ValueError("Amount must be positive")
if payment_method == "credit_card":
return process_credit_card(amount)
elif payment_method == "paypal":
return process_paypal(amount)
else:
raise ValueError("Unknown payment method")
# Если у нас есть тесты только для credit_card,
# то paypal и ValueError в else никогда не будут проверены
Инструмент coverage покажет, что часть кода не протестирована, и мы добавим необходимые тесты.
2. Снижение риска багов в production
Нетестированный код — основной источник багов в production:
# Без coverage можно не заметить
def calculate_discount(customer_type, purchase_amount):
if customer_type == "premium":
return purchase_amount * 0.1 # 10% скидка
elif customer_type == "vip":
return purchase_amount * 0.2 # 20% скидка
# Что произойдёт при customer_type == "regular"?
# Функция вернёт None вместо 0!
# Coverage покажет, что случай regular не покрыт
# Мы добавим тест и исправим баг
def calculate_discount(customer_type, purchase_amount):
discount_rates = {
"premium": 0.1,
"vip": 0.2,
"regular": 0.0
}
return purchase_amount * discount_rates.get(customer_type, 0.0)
3. Уверенность при рефакторинге
Хорошее покрытие позволяет безопасно менять код без страха сломать функциональность:
# Старый код
def get_user_age(user_id):
user = User.objects.get(id=user_id)
return user.birth_date.year - datetime.now().year
# Хотим переписать, но есть тесты
def test_user_age():
user = User.objects.create(birth_date=date(2000, 1, 1))
assert get_user_age(user.id) >= 23
# Переписываем (исправляем баг с неточностью)
def get_user_age(user_id):
user = User.objects.get(id=user_id)
today = date.today()
age = today.year - user.birth_date.year
if (today.month, today.day) < (user.birth_date.month, user.birth_date.day):
age -= 1
return age
# Тест пройдёт - знаем, что не сломали логику
4. Требование профессионального разработчика
Любая серьёзная компания требует определённый уровень покрытия:
# pyproject.toml
[tool.pytest.ini_options]
addopts = "--cov=src --cov-fail-under=85"
# Это значит: если покрытие ниже 85%, тесты НЕ пройдут
# Merge в main будет заблокирован
Типы покрытия
1. Line Coverage (покрытие строк)
Процент строк кода, которые были выполнены:
def validate_email(email):
if not email: # Строка 1
return False # Строка 2 (не покрыта, если нет теста с пустой строкой)
if "@" not in email: # Строка 3
return False # Строка 4
return True # Строка 5
# Если тест только проверяет validate_email("test@test.com")
# То покрытие = 3/5 = 60% (пропущены строки 2 и 4)
2. Branch Coverage (покрытие ветвей)
Процент условных ветвей, которые были выполнены:
def process_order(quantity, is_member):
cost = quantity * 10
if is_member: # Ветвь 1: if True
cost *= 0.9 # Скидка 10%
else: # Ветвь 2: if False
cost *= 1.0 # Без скидки
return cost
# Если тесты только проверяют is_member=True,
# то покрытие веток = 50% (не протестирована ветвь else)
3. Function Coverage (покрытие функций)
Процент функций, которые были вызваны:
def calculate_total(items): # Функция 1
return sum(items)
def apply_tax(total): # Функция 2
return total * 1.2
def checkout(items): # Функция 3
total = calculate_total(items)
return apply_tax(total)
# Если тесты только вызывают checkout(),
# то функция покрытия = 100% (все функции вызваны)
# Но line coverage может быть меньше
Как использовать Coverage в Python
pytest с coverage
# Установка
pip install pytest-cov
# Запуск тестов с отчётом о покрытии
pytest --cov=src --cov-report=html
# Результаты в htmlcov/index.html
Пример конфига
# .coveragerc или pyproject.toml
[tool.coverage.run]
branch = True # Покрытие веток
source = ["src"]
[tool.coverage.report]
precision = 2
show_missing = True # Показывать пропущенные строки
fail_under = 85 # Ошибка если < 85%
[tool.coverage.html]
directory = "htmlcov"
Интеграция в CI/CD
# GitHub Actions
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: pip install pytest pytest-cov
- run: pytest --cov=src --cov-fail-under=85
# Если покрытие ниже 85%, PR не может быть слит
Лучшие практики
1. Не гонись за числом, стремись к качеству
# Плохо - тест, который ничего не проверяет
def test_function():
result = my_function() # Тест ничего не assert
# Это повышает coverage, но не проверяет логику!
# Хорошо
def test_function():
result = my_function(10)
assert result == 20 # Что-то проверяем
2. Целевое покрытие 85-95%
# 100% coverage часто невозможно:
# - обработка редких ошибок (network timeout)
# - платформо-специфичный код
# - deprecated функции
# 85% - хороший баланс между усилиями и защитой
3. Покрывай критичный код в первую очередь
# Приоритет 1: бизнес-логика (платежи, расчёты)
def process_payment(amount): ... # Must have 100%
# Приоритет 2: важные пути (auth, validation)
def validate_user(user): ... # Должно быть 95%+
# Приоритет 3: утилиты, helpers
def format_date(date): ... # 70-80% нормально
4. Используй coverage как инструмент, а не метрику
# Coverage показывает где есть дыры, но не гарантирует качество
# 95% coverage с плохими тестами хуже, чем 70% с хорошими
# Хорошие тесты:
# - Проверяют граничные случаи
# - Проверяют ошибки
# - Проверяют интеграцию
# - Читаемые и поддерживаемые
Реальный пример проекта
# Структура проекта
src/
users/
__init__.py
models.py # Models - должны покрыть на 100%
services.py # Business logic - 95%
schemas.py # Schemas - 85%
handlers.py # API handlers - 90%
tests/
unit/
test_user_models.py
test_user_services.py
integration/
test_user_api.py
# Команда
make test # Запускает pytest --cov
# Результат
# Name Stmts Miss Cover Missing
# ────────────────────────────────────────────────────
# src/users/__init__.py 5 0 100%
# src/users/models.py 42 0 100%
# src/users/services.py 58 3 95% 145, 156, 189
# src/users/schemas.py 31 5 84% 72, 73, 91, 92, 95
# ────────────────────────────────────────────────────
# TOTAL 136 8 94%
Заключение
Code Coverage — это не просто число. Это:
- Инструмент для поиска неотестированного кода
- Защита от регрессии при рефакторинге
- Стандарт качества в профессиональной разработке
- Confidence перед развёртыванием в production