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". Краще цього уникнути.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ