← Назад к вопросам
Измеряешь ли процент покрытия тестов
2.0 Middle🔥 61 комментариев
#Базы данных (SQL)#Безопасность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Покрытие тестами (Test Coverage)
Да, я активно измеряю и отслеживаю процент покрытия тестами. Это один из ключевых показателей качества кода и надежности приложения. Покрытие показывает, какой процент кода действительно выполняется во время тестирования.
Почему я измеряю покрытие
1. Гарантия качества
# Если код не покрыт тестами, его поведение неизвестно
def process_payment(amount, discount=0):
total = amount - discount
if total < 0:
raise ValueError('Invalid total')
charge_card(total) # Когда это выполняется?
return total
# Без тестов на path с ошибкой нельзя гарантировать,
# что charge_card() не вызовется при отрицательной сумме
2. Раннее обнаружение багов
# Низкое покрытие = непроверенный код
# Непроверенный код = потенциальные баги
def calculate_discount(customer_type, amount):
if customer_type == 'premium':
return amount * 0.2
elif customer_type == 'regular':
return amount * 0.1
# Что если customer_type = 'unknown'?
# Вернет None, затем может быть ошибка типа
# С тестом покрытия мы бы заметили этот edge case
3. Рефакторинг с уверенностью
# С хорошим покрытием можно безопасно менять код
# Если тесты пройдут, работа в порядке
def old_implementation(data):
result = []
for item in data:
if item > 0:
result.append(item * 2)
return result
def new_implementation(data):
return [item * 2 for item in data if item > 0]
# Если есть покрытие, мы знаем что both работают идентично
Инструменты для измерения покрытия
Coverage.py — стандартный инструмент
# Установка
pip install coverage
# Запуск тестов с измерением покрытия
coverage run -m pytest
# Вывод отчета
coverage report
# Детальный отчет в HTML
coverage html
open htmlcov/index.html
# Report по файлам
coverage report src/
Конфигурация .coveragerc
[run]
branch = True
source = src
omit =
*/migrations/*
*/tests/*
setup.py
[report]
precision = 2
show_missing = True
skip_covered = False
skip_empty = True
[html]
directory = htmlcov
Пример проекта с тестами
# src/calculator.py
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def divide(a, b):
if b == 0:
raise ValueError('Cannot divide by zero')
return a / b
# tests/test_calculator.py
import pytest
from src.calculator import add, subtract, divide
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
def test_subtract():
assert subtract(5, 3) == 2
assert subtract(-1, -1) == 0
def test_divide():
assert divide(10, 2) == 5
assert divide(7, 2) == 3.5
def test_divide_by_zero():
with pytest.raises(ValueError):
divide(10, 0)
# Запуск с покрытием
$ coverage run -m pytest
$ coverage report
Name Stmts Miss Cover
-------------------------------------------
src/calculator.py 11 0 100%
tests/test_calculator.py 20 0 100%
-------------------------------------------
TOTAL 31 0 100%
Минимальные требования к покрытию
В production коде
Основная логика: >= 90%
Вспомогательный код: >= 70%
Общий проект: >= 80%
Что обязательно должно быть покрыто
# ✅ Основные пути выполнения
def process_order(order):
if order.total < 100: # MUST TEST
apply_discount(order)
else:
apply_premium_discount(order)
return order
# ✅ Обработка ошибок
def fetch_user(user_id):
if not user_id:
raise ValueError('ID required') # MUST TEST
return get_user(user_id)
# ✅ Edge cases
def calculate_average(numbers):
if not numbers: # MUST TEST
return 0
return sum(numbers) / len(numbers)
Что НЕ нужно покрывать на 100%
# 1. Обработка очень редких исключений
try:
operation()
except MemoryError: # Редко тестируем
log_critical_error()
# 2. Утилиты внутренних тестовых фреймворков
if __name__ == '__main__': # Обычно не покрываем
main()
# 3. Импорты и type hints
from typing import Optional # Не покрываем
from dataclasses import dataclass # Не покрываем
Типичные проблемы с покрытием
Проблема 1: Игнорирование низкого покрытия
# ❌ Плохо: не проверяем покрытие
def run_tests():
pytest.main(['tests/'])
# Не проверяем процент покрытия
# ✅ Хорошо: требуем определенное покрытие
def run_tests():
result = pytest.main(['tests/', '--cov=src', '--cov-fail-under=80'])
# Если покрытие < 80%, тест падает
Проблема 2: Фальшивые тесты (охватывают код, но не тестируют логику)
# ❌ Плохо: тест просто вызывает функцию
def test_process_order():
result = process_order(create_test_order())
# Не проверяем результат!
# Покрытие есть, но логики нет
# ✅ Хорошо: тест проверяет результат
def test_process_order():
order = create_test_order()
result = process_order(order)
assert result.total == expected_total
assert result.status == 'processed'
Проблема 3: Избыточное тестирование
# ❌ Плохо: тестируем библиотечный код
def test_json_parsing():
data = json.loads('{"key": "value"}')
assert data['key'] == 'value'
# Это тест json модуля, не нашего кода!
# ✅ Хорошо: тестируем нашу логику
def test_parse_user_data():
user_json = '{"name": "Alice", "age": 30}'
user = parse_user_data(user_json)
assert user.name == 'Alice'
assert user.age == 30
Интеграция покрытия в CI/CD
GitHub Actions
name: Tests and Coverage
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.11
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests with coverage
run: |
coverage run -m pytest
coverage report
coverage html
- name: Fail if coverage too low
run: coverage report --fail-under=80
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
GitLab CI
test:
image: python:3.11
script:
- pip install -r requirements.txt
- coverage run -m pytest
- coverage report --fail-under=80
coverage: '/TOTAL.*\s+(\d+%)$/'
Мой подход к покрытию
1. Минимум 80% для основного кода
# В pyproject.toml
[tool.coverage.report]
fail_under = 80
show_missing = True
2. 100% для критичного кода
# src/payment/processor.py — критичный модуль
# MUST: 100% coverage
# Любая функция в платежах должна быть протестирована
3. Ветвовое покрытие (branch coverage)
# Обычное покрытие: строка выполнена или нет
# Ветвовое: все условия в if/else протестированы
coverage run --branch -m pytest
coverage report
4. Еженедельный мониторинг
# Смотрю тренд покрытия
coverage report > coverage_$(date +%Y%m%d).txt
# Если покрытие падает — ищу почему
# Новый код без тестов? Рефакторинг? Удаления?
Практический пример из реального проекта
# models/user.py
from datetime import datetime
from pydantic import BaseModel, EmailStr
class User(BaseModel):
id: int
email: EmailStr
name: str
age: int
created_at: datetime = None
def __init__(self, **data):
super().__init__(**data)
if self.created_at is None:
self.created_at = datetime.now()
def is_adult(self) -> bool:
return self.age >= 18
def formatted_email(self) -> str:
return self.email.lower()
# tests/test_user.py
import pytest
from datetime import datetime
from models.user import User
def test_user_creation():
user = User(
id=1,
email='alice@example.com',
name='Alice',
age=30
)
assert user.id == 1
assert user.email == 'alice@example.com'
assert user.created_at is not None
def test_user_is_adult():
adult = User(id=1, email='alice@example.com', name='Alice', age=30)
minor = User(id=2, email='bob@example.com', name='Bob', age=15)
assert adult.is_adult() is True
assert minor.is_adult() is False
def test_formatted_email():
user = User(
id=1,
email='ALICE@EXAMPLE.COM',
name='Alice',
age=30
)
assert user.formatted_email() == 'alice@example.com'
$ coverage run -m pytest tests/test_user.py
$ coverage report models/user.py
Name Stmts Miss Cover
--------------------------------------
models/user.py 15 0 100%
Итоговое резюме
Я активно использую покрытие тестами потому что:
- Гарантирует качество — непокрытый код = потенциальные баги
- Дает уверенность — изменяю код без страха
- Выявляет edge cases — низкое покрытие показывает непротестированные пути
- Автоматизируется — интегрируется в CI/CD
- Экономит время — лучше найти баг в тестах, чем в production
Мой стандарт:
- Основной код: минимум 80%
- Критичный код (платежи, auth): 100%
- Ветвовое покрытие: включено
- Требование к покрытию: в CI/CD