JavaRush /Курсы /Модуль 4: FastAPI /Тестирование обработки ошибок и исключений

Тестирование обработки ошибок и исключений

Модуль 4: FastAPI
5 уровень , 5 лекция
Открыта

Обработка ошибок — это один из краеугольных камней качественного API. Пользователи, сталкивающиеся с непредсказуемыми ошибками (например, "500 Internal Server Error"), теряют доверие к приложению. А разработчикам, в свою очередь, важно, чтобы такие ошибки были не только понятными для пользователя, но и легко отлаживаемыми.

FastAPI предоставляет удобные инструменты для генерации и обработки ошибок. С помощью стандартных исключений, таких как HTTPException, можно возвращать подробные, корректно отформатированные ответы с ошибками.

Общие принципы обработки ошибок:

  1. Ясность: сообщения об ошибках должны быть понятными и содержать только ту информацию, которая важна для пользователя (любой ненужной информации — "до свидания").
  2. Консистентность: весь API должен возвращать ошибки в одном формате.
  3. Информативность: для разработчиков важно, чтобы ошибки содержали максимум полезной информации.

Типы ошибок, которые следует тестировать

Ваше приложение может сталкиваться с различными сценариями ошибок:

  • Ошибки клиента (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"

Особенности и типичные ошибки при тестировании ошибок

Иногда в процессе тестирования ошибок можно столкнуться с трудностями:

  1. Неправильные ожидания статусов. Помните, что API должен возвращать ПРАВИЛЬНЫЕ коды статусов. Например, ошибка валидации данных возвращает 422, а не 400.
  2. Несоответствие сообщений. Убедитесь, что текст сообщения об ошибке совпадает с тем, что ожидает тест.
  3. Пропущенные кейсы. Всегда тестируйте как успешные сценарии, так и ошибки. Например, что произойдет, если передать пустое тело POST-запроса?

Как это применить на практике?

Хорошо протестированное API:

  • Вызывает меньше сюрпризов при передаче управления другим разработчикам.
  • Создает позитивные впечатления у пользователей благодаря понятным и предсказуемым ошибкам.
  • Проходит проверки качества на собеседованиях и в процессе ревью кода.

И да, кто не тестирует обработку ошибок, тот однажды увидит в продакшене надпись "Oops... Something Bad Happened". Лучше уж этого избежать.

1
Задача
Модуль 4: FastAPI, 5 уровень, 5 лекция
Недоступна
Тестирование ошибки клиента (400 Bad Request)
Тестирование ошибки клиента (400 Bad Request)
1
Задача
Модуль 4: FastAPI, 5 уровень, 5 лекция
Недоступна
Тестирование серверной ошибки (500 Internal Server Error)
Тестирование серверной ошибки (500 Internal Server Error)
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ