Представьте себе одинокого разработчика, который пишет тесты "на глаз". Он может подумать, что протестировал код полностью, но суровая реальность покажет, что половина логики осталась без внимания. Вот почему аналитика покрытия кода (code coverage) — это как персональный прожектор в тёмной пещере вашего приложения. Она подсвечивает, какие участки кода активируются при выполнении тестов, а какие нет.
Понимание покрытия кода решает следующие задачи:
- Выявляет необработанные сценарии и пропущенные ветви кода.
- Помогает избежать критических ошибок и багов в продакшене.
- Даёт командному лидеру (и вам) доказательства вашей крутости в виде чисел.
Установка инструментов для замеров покрытия
Для анализа покрытия кода в Pytest используется замечательный плагин — pytest-cov. Он настолько простой, что его установка займёт у вас меньше времени, чем загрузка мемов про JavaScript. Просто установите плагин через pip:
pip install pytest-cov
После этого плагин автоматически интегрируется в Pytest, и вы сможете получать метрики покрытия кода без сложных настроек.
Если вы работаете в виртуальном окружении (а кто из вас не работает в виртуальном окружении после нашей лекции про Docker?), убедитесь, что pytest-cov установлен там.
Запуск тестов с замером покрытия
Настало время запустить наши тесты и измерить, насколько хорошо мы прикрыли код! Используйте следующую команду:
pytest --cov=app
Где app — это директория с вашим FastAPI-приложением. Pytest выполнит все тесты, а затем выдаст отчёт о покрытии.
Пример вывода:
---------- coverage: platform linux, python 3.10.0 ----------
Name Stmts Miss Cover
-------------------------------------------------------------
app/main.py 20 2 90%
app/routers/example.py 50 10 80%
-------------------------------------------------------------
TOTAL 100 12 88%
Что здесь происходит:
- Stmts: Общее количество строк кода.
- Miss: Строки кода, которые не были выполнены во время тестов.
- Cover: Процент покрытия кода.
Если ваш процент покрытия слишком низок (<70%), это повод для сомнений… и написания новых тестов.
Улучшение формата отчёта: вывод в HTML
Консольный отчёт — это здорово, но давайте сделаем его немного наряднее. Вы можете сгенерировать HTML-отчёт, который легко визуально анализировать. Для этого добавьте флаг --cov-report=html:
pytest --cov=app --cov-report=html
После выполнения этой команды вы найдёте отчёт в папке htmlcov. Просто откройте файл index.html в браузере, и вы увидите красочную интерактивную страницу с подробной информацией о покрытии.
Особенность HTML-отчёта в том, что вы увидите:
- Какие строки кода были выполнены (они подсвечиваются зелёным).
- Какие строки кода не были выполнены (красное подсвечивание).
Автоматизация тестов в CI/CD
Если вы хотите, чтобы ваши тесты запускались автоматически при каждом коммите в репозиторий, вам нужно интегрировать тестирование в систему CI/CD. Возьмём для примера GitHub Actions. Создайте файл .github/workflows/test.yml со следующим содержимым:
name: Run Tests
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests with coverage
run: |
pytest --cov=app --cov-report=xml
Здесь:
- Тесты запускаются автоматически при каждом пуше в ветку
main. - Результаты покрытия сохраняются в формате XML для дальнейшего анализа.
Интерпретация и исправление "дыр" в покрытии
После анализа отчёта о покрытии вы можете заметить, что некоторые ветви логики остаются непокрытыми. Например, ваш API может обрабатывать HTTP-ошибки типа 400 или 404, но тесты проверяют только успешные кейсы.
Попробуем улучшить тестирование примера из прошлой лекции — обработку ошибок:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id < 1:
raise HTTPException(status_code=400, detail="Invalid item ID")
return {"item_id": item_id}
Теперь напишем тесты на этот эндпоинт:
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_read_item_success():
# Проверяем успешный запрос
response = client.get("/items/1")
assert response.status_code == 200
assert response.json() == {"item_id": 1}
def test_read_item_invalid():
# Проверяем обработку ошибки
response = client.get("/items/0")
assert response.status_code == 400
assert response.json() == {"detail": "Invalid item ID"}
С этими тестами вы увидите, как процент покрытия растёт, а "дыры" в логике постепенно устраняются.
Советы по работе с покрытием
1. Не стремитесь к 100%
Достижение 100% покрытия — это похвальная цель, но в реальных проектах это не всегда необходимо. Важно тестировать ключевые ветви логики, а не весь тривиальный код (например, геттеры и сеттеры).
2. Уделяйте внимание сложным ветвям
Сценарии, где код принимает решения на основе условий, требуют особого внимания. Убедитесь, что вы покрыли разные варианты исполнения этих ветвей.
3. Используйте отчёты для рефакторинга
Покрытие кода помогает не только выявлять пропуски, но и находить мёртвый код, который можно удалить без вреда.
Заключительный пример: полный отчёт о тестах и покрытии
Соберём всё воедино. Допустим, у нас есть приложение с несколькими эндпоинтами. Нам нужно написать тесты, покрывающие всю логику. После этого мы запускаем:
pytest --cov=app --cov-report=html
В HTML-отчёте мы видим:
- Успешно покрытые эндпоинты выделены зелёным.
- Пропущенные сценарии подсвечены красным.
Мы анализируем пропуски, пишем тесты для их покрытия и повторяем процесс до тех пор, пока не достигнем приемлемого уровня.
Теперь вы вооружены всеми нужными инструментами и знаниями, чтобы сделать своё приложение надёжным и готовым к реальному миру. Готовьтесь к хвалебным отзывам коллег (и, возможно, HR-ов на собеседованиях).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ