← Назад к вопросам

Зачем нужен Coverage?

1.3 Junior🔥 201 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Зачем нужен 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