Обработка ошибок — это один из краеугольных камней качественного API. Пользователи, сталкивающиеся с непредсказуемыми ошибками (например, "500 Internal Server Error"), теряют доверие к приложению. А разработчикам, в свою очередь, важно, чтобы такие ошибки были не только понятными для пользователя, но и легко отлаживаемыми.
FastAPI предоставляет удобные инструменты для генерации и обработки ошибок. С помощью стандартных исключений, таких как HTTPException, можно возвращать подробные, корректно отформатированные ответы с ошибками.
Общие принципы обработки ошибок:
- Ясность: сообщения об ошибках должны быть понятными и содержать только ту информацию, которая важна для пользователя (любой ненужной информации — "до свидания").
- Консистентность: весь API должен возвращать ошибки в одном формате.
- Информативность: для разработчиков важно, чтобы ошибки содержали максимум полезной информации.
Типы ошибок, которые следует тестировать
Ваше приложение может сталкиваться с различными сценариями ошибок:
- Ошибки клиента (4xx HTTP Status Code): некорректный запрос, несоответствие данных, отсутствующие параметры и т.д.
- Ошибки сервера (5xx HTTP Status Code): неожиданные сбои, отсутствующие зависимости, ошибки в логике сервера.
- Пользовательские исключения. Например, бизнес-логика, специфичная только для вашего приложения (недостаток средств, неверный пароль, превышение лимитов).
Давайте разбираться, как писать тесты для обработки всех этих случаев.
Генерация и обработка ошибок в FastAPI
FastAPI делает обработку ошибок настолько простой, что это вызывает слезы счастья у разработчиков (даже у тех, кто уже "испытал всё"). Основным инструментом является HTTPException, который позволяет возвращать HTTP-ответы с кодами ошибок.
Пример обработки ошибок в FastAPI
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id <= 0:
# Генерируем исключение при некорректном ID
raise HTTPException(status_code=400, detail="Item ID must be positive.")
# Вернем фейковый объект
return {"item_id": item_id, "name": "Test Item"}
В этом примере при запросе с некорректным item_id (например, отрицательное значение) мы возвращаем 400 Bad Request с описанием ошибки.
Тесты для обработки ошибок
Приступим, наконец, к главному — написанию тестов для обработки ошибок. Для этого нам потребуется использовать TestClient из FastAPI.
Тестирование ошибок клиента (4xx)
Начнем с тестирования нашего эндпоинта /items/{item_id} на случай некорректного item_id.
from fastapi.testclient import TestClient
from main import app # Подключаем наше приложение
client = TestClient(app)
def test_read_item_invalid_id():
# Отрицательный item_id вызывает ошибку 400
response = client.get("/items/-1")
assert response.status_code == 400
assert response.json() == {"detail": "Item ID must be positive."}
Здесь мы проверяем, что при передаче отрицательного item_id API возвращает корректный HTTP-статус (400) и правильное сообщение об ошибке.
Тестирование ошибок сервера (5xx)
Представим, что в нашем приложении произошла неожиданная ошибка, например, разрыв соединения с базой данных. Мы хотим убедиться, что такие сценарии корректно обрабатываются.
Пример эндпоинта с искусственным вызовом ошибки 500:
@app.get("/simulate_server_error")
async def simulate_error():
raise ValueError("Something went horribly wrong!")
Напишем тест:
def test_server_error():
response = client.get("/simulate_server_error")
assert response.status_code == 500
# Проверяем, что ошибка обработана в формате FastAPI
assert "detail" in response.json()
Тест пользовательских исключений
Иногда ошибки связаны с нарушением бизнес-логики. Например, при попытке перевести больше денег, чем есть на счету пользователя.
Эндпоинт для перевода денег:
from fastapi import HTTPException
@app.post("/transfer")
async def transfer_funds(amount: float):
if amount > 1000.0: # Логика
raise HTTPException(
status_code=400,
detail="Transfer amount exceeds the limit of $1000.00",
)
return {"status": "success", "transferred": amount}
Тестируем превышение лимита перевода:
def test_transfer_amount_exceeds_limit():
response = client.post("/transfer", json={"amount": 1500.0})
assert response.status_code == 400
assert response.json()["detail"] == "Transfer amount exceeds the limit of $1000.00"
Обработка ошибок через глобальные обработчики
FastAPI позволяет централизованно обрабатывать исключения с помощью декоратора @app.exception_handler. Это полезно для обработки ошибок, которые могут возникать в разных частях приложения.
Настройка глобального обработчика ошибок
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return JSONResponse(
status_code=422,
content={"detail": "Input validation failed", "errors": exc.errors()},
)
Теперь все ошибки валидации автоматически будут возвращать унифицированный ответ.
Тестирование глобальных обработчиков
Если вы добавляете глобальные обработчики, обязательно тестируйте их работу.
Пример теста для кастомного обработчика валидации:
def test_validation_error():
response = client.get("/items/string_instead_of_int")
assert response.status_code == 422
assert response.json()["detail"] == "Input validation failed"
Особенности и типичные ошибки при тестировании ошибок
Иногда в процессе тестирования ошибок можно столкнуться с трудностями:
- Неправильные ожидания статусов. Помните, что API должен возвращать ПРАВИЛЬНЫЕ коды статусов. Например, ошибка валидации данных возвращает 422, а не 400.
- Несоответствие сообщений. Убедитесь, что текст сообщения об ошибке совпадает с тем, что ожидает тест.
- Пропущенные кейсы. Всегда тестируйте как успешные сценарии, так и ошибки. Например, что произойдет, если передать пустое тело POST-запроса?
Как это применить на практике?
Хорошо протестированное API:
- Вызывает меньше сюрпризов при передаче управления другим разработчикам.
- Создает позитивные впечатления у пользователей благодаря понятным и предсказуемым ошибкам.
- Проходит проверки качества на собеседованиях и в процессе ревью кода.
И да, кто не тестирует обработку ошибок, тот однажды увидит в продакшене надпись "Oops... Something Bad Happened". Лучше уж этого избежать.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ