Уявіть собі самотнього розробника, який пише тести "на око". Він може подумати, що протестував код повністю, але сувора реальність покаже, що половина логіки залишилась без уваги. Ось чому аналітика покриття коду (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-ів на співбесідах).
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ