Сегодня мы напишем полный набор тестов для 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": "User not found"}
Использование фикстур
Чтобы избежать дублирования кода (например, создания пользователей в каждом тесте), мы можем использовать фикстуры 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 готово к любым испытаниям.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ