Сьогодні ми напишемо повний набір тестів для FastAPI-додатка. Ми пройдемося по всіх ключових можливостях API і переконаємося, що наш додаток працює як годинник. Для цього ми:
- Навчимося організовувати тести так, щоб вони були структурованими;
- Покриємо основні кейси, включаючи захищені ендпоінти, обробку помилок і перевірку бізнес-логіки;
- Розберемося, як писати тести, які не тільки перевіряють працездатність додатка, але й допомагають знайти кути, про які ми могли не подумати.
Приклад додатка: база даних користувачів
Для тестування візьмемо невеликий додаток, який ми розробляли раніше — базу даних користувачів з CRUD-операціями. Ось його основні ендпоінти:
GET /users/— отримання списку користувачів.GET /users/{user_id}— отримання даних конкретного користувача.POST /users/— створення нового користувача.PUT /users/{user_id}— оновлення даних користувача.DELETE /users/{user_id}— видалення користувача.
Крім того, додаток підтримує:
- Валідацію даних через Pydantic;
- Аутентифікацію через JWT;
- Обробку кастомних помилок (наприклад, якщо користувача не знайдено).
Перед тестуванням переконайтеся, що структура вашого проєкту виглядає приблизно так:
project/
├── app/
│ ├── main.py
│ ├── models.py
│ ├── schemas.py
│ ├── database.py
│ ├── routers/
│ │ └── users.py
├── tests/
│ └── test_users.py
План тестування
Весь процес тестування розіб'ємо на наступні етапи:
- Тестування публічних ендпоінтів (GET-запити).
- Тестування захищених ендпоінтів (POST, PUT, DELETE).
- Перевірка обробки помилок.
- Робота з фікстурами для прискорення написання тестів.
- Перевірка покриття коду.
Тестування публічних ендпоінтів
Спочатку протестуємо, як працює отримання даних через GET /users/ і GET /users/{user_id}. Для цього ми будемо використовувати вбудований TestClient з FastAPI.
Приклад тесту для отримання списку користувачів
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_get_all_users():
response = client.get("/users/")
assert response.status_code == 200
assert isinstance(response.json(), list) # Відповідь має бути списком
Приклад тесту для отримання конкретного користувача
def test_get_user_by_id():
user_id = 1
response = client.get(f"/users/{user_id}")
assert response.status_code == 200
data = response.json()
assert data["id"] == user_id
assert "email" in data # Перевіряємо наявність очікуваних полів
Тестування захищених ендпоінтів
Оскільки деякі ендпоінти вимагають аутентифікацію, нам потрібно передавати коректний JWT-токен. У реальних додатках ви, швидше за все, будете тестувати не тільки коректні токени, але й сценарії, коли вони відсутні або невалідні.
Фікстура для створення токена
import jwt
from datetime import datetime, timedelta
SECRET_KEY = "supersecretkey"
def get_test_token(user_id: int):
payload = {
"sub": user_id,
"exp": datetime.utcnow() + timedelta(minutes=15)
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
Приклад тесту для створення користувача
def test_create_user():
token = get_test_token(user_id=1)
headers = {"Authorization": f"Bearer {token}"}
payload = {
"email": "testuser@example.com",
"name": "Test User",
"age": 25
}
response = client.post("/users/", json=payload, headers=headers)
assert response.status_code == 201
data = response.json()
assert data["email"] == payload["email"]
Тестування обробки помилок
Помилки — це як баги, тільки очікувані. Тому ми повинні переконатися, що наш додаток коректно обробляє ситуації, коли, наприклад, користувач запитує неіснуючий ресурс.
Приклад тесту для отримання неіснуючого користувача
def test_get_nonexistent_user():
user_id = 999 # Якщо такого користувача немає
response = client.get(f"/users/{user_id}")
assert response.status_code == 404
assert response.json() == {"detail": "Користувача не знайдено"}
Використання фікстур
Щоб уникнути дублювання коду (наприклад, створення користувачів у кожному тесті), ми можемо використовувати фікстури Pytest.
Фікстура для підготовки даних
import pytest
@pytest.fixture
def new_user():
return {"email": "fixtureuser@example.com", "name": "Fixture User", "age": 30}
Використання фікстури в тестах
def test_create_user_with_fixture(new_user):
token = get_test_token(user_id=1)
headers = {"Authorization": f"Bearer {token}"}
response = client.post("/users/", json=new_user, headers=headers)
assert response.status_code == 201
assert response.json()["email"] == new_user["email"]
Перевірка покриття коду
Після написання всіх тестів ми повинні переконатися, що покрили більшу частину нашого додатка. Для цього ми будемо використовувати плагін pytest-cov.
Встановлення pytest-cov:
pip install pytest-cov
Запуск тестів з покриттям:
pytest --cov=app
Результат виглядатиме приблизно так:
----------- coverage: platform linux, python 3.x -----------
Name Stmts Miss Cover
-----------------------------------------------------
app/main.py 18 0 100%
app/models.py 15 2 87%
app/schemas.py 21 1 95%
app/routers/users.py 42 3 93%
-----------------------------------------------------
TOTAL 96 6 94%
Організація структури тестів
Якщо ваші тести розростуться, можливо, ви захочете організувати їх за модулями або функціональністю. Наприклад:
tests/
├── test_users.py
├── test_auth.py
├── test_errors.py
Підсумковий приклад повного набору тестів
Ось прикладні тести, які можна включити в ваш проєкт:
- Тести для всіх CRUD-операцій (
GET,POST,PUT,DELETE); - Перевірка захисних механік (валідація токенів, доступ до захищених ресурсів);
- Обробка помилок (404, некоректні дані);
- Тестування з використанням фікстур;
- Переконатися в високому рівні покриття коду.
З таким підходом ви будете спати спокійно і знати, що ваше API готове до будь-яких випробувань.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ