Как писать тесты для моделей Pydantic в FastAPI?

avatar
PercySherlock
9 августа 2021 в 00:27
4187
1
4

Я только начал использовать FastAPI, но не знаю, как написать модульный тест (используя pytest) для модели Pydantic.

Вот пример модели Pydantic:

class PhoneNumber(BaseModel):
    id: int
    country: str
    country_code: str
    number: str
    extension: str

Я хочу протестировать эту модель, создав образец экземпляра PhoneNumber и убедиться, что экземпляр PhoneNumber соответствует типам полей. Например:

PhoneNumber(1, "country", "code", "number", "extension")

Тогда я хочу утверждать, что PhoneNumber.country равно "стране".

Источник
Gino Mempin
9 августа 2021 в 00:32
0

Что, по вашему мнению, должен проверить тест? Как это связано с FastAPI? Поскольку вы можете использовать модели pydantic без с помощью FastAPI.

PercySherlock
9 августа 2021 в 00:42
0

@GinoMempin Пожалуйста, проверьте мое редактирование.

PercySherlock
9 августа 2021 в 00:52
2

@ Марк Да, это было. Я внес изменения. Спасибо, что указали на это.

Mark
9 августа 2021 в 01:00
5

Вообще говоря, вы не пишете такие тесты. У Pydantic есть хороший набор тестов (включая модульный тест, подобный тому, который вы предлагаете). Ваш тест должен охватывать написанный вами код и логику, а не импортированные вами пакеты.

PercySherlock
9 августа 2021 в 01:08
0

@Марк О! Я понимаю. Большое спасибо!

Gino Mempin
9 августа 2021 в 01:43
3

Поскольку вы пометили это с помощью FastAPI и если вы используете эту модель как часть маршрута (либо в качестве параметра запроса, либо в качестве модели ответа), более полезным тестом является проверка того, что вызов вашего маршрута/API правильно использует вашу модель ( например, передача тела JSON правильно преобразуется в вашу модель PhoneNumber).

PercySherlock
9 августа 2021 в 02:16
2

@GinoMempin Теперь я понимаю. Спасибо!

Ответы (1)

avatar
Gino Mempin
10 августа 2021 в 12:13
10

Тест, которого вы хотите достичь, легко выполнить с помощью pytest. Никаких особых хитростей не требуется, просто следуйте документам и руководствам pytest:

import pytest

def test_phonenumber():
    pn = PhoneNumber(id=1, country="country", country_code="code", number="number", extension="extension")

    assert pn.id == 1
    assert pn.country == 'country'
    assert pn.country_code == 'code'
    assert pn.number == 'number'
    assert pn.extension == 'extension'

Но я согласен с этим комментарием:

Вообще говоря, такие тесты не пишутся. Пидантик имеет хороший набор тестов (включая модульный тест, подобный тому, который вы предлагаете) . Ваш тест должен охватывать написанный вами код и логику, а не пакеты, которые вы импортировали.

Если у вас есть такая модель, как модель PhoneNumber, которая довольно проста и не имеет «особого»/«причудливого» поведения, то написание тестов, которые просто создают ее экземпляры и проверяют атрибуты, не будет таким полезным. Подобные тесты подобны тестированию самого Pydantic.

Если, однако, ваша модель имеет некоторые функции-валидаторы, которые выполняют какие-то "специальные"/"причудливые" проверки, например, она проверяет соответствие country и country_code:

from pydantic import BaseModel, root_validator

class PhoneNumber(BaseModel):
    ...

    @root_validator(pre=True)
    def check_country(cls, values):
        """Check that country_code is the 1st 2 letters of country"""
        country: str = values.get('country')
        country_code: str = values.get('country_code')
        if not country.lower().startswith(country_code.lower()):
            raise ValueError('country_code and country do not match')
        return values

... тогда модульный тест для этого конкретного поведения был бы более полезен:

import pytest

def test_phonenumber_country_code():
    """Expect test to fail because country_code and country do not match"""
    with pytest.raises(ValueError):
        PhoneNumber(id=1, country='JAPAN', country_code='XY', number='123', extension='456')

Кроме того, как я упоминал в моем комментарии, поскольку вы упомянули FastAPI, если вы используете эту модель как часть определения маршрута (будь то параметр запроса или модель ответа), то более полезным тестом было бы убедиться, что ваш маршрут может правильно использовать вашу модель.

@app.post("/phonenumber")
async def add_phonenumber(phonenumber: PhoneNumber):
    """The model is used here as part of the Request Body"""
    # Do something with phonenumber
    return JSONResponse({'message': 'OK'}, status_code=200)
from fastapi.testclient import TestClient

client = TestClient(app)

def test_add_phonenumber_ok():
    """Valid PhoneNumber, should be 200/OK"""
    # This would be what the JSON body of the request would look like
    body = {
        "id": 1,
        "country": "Japan",
        "country_code": "JA",
        "number": "123",
        "extension": "81",
    }
    response = client.post("/phonenumber", json=body)
    assert response.status_code == 200


def test_add_phonenumber_error():
    """Invalid PhoneNumber, should be a validation error"""
    # This would be what the JSON body of the request would look like
    body = {
        "id": 1,
        "country": "Japan",
                             # `country_code` is missing
        "number": 99999,     # `number` is int, not str
        "extension": "81",
    }
    response = client.post("/phonenumber", json=body)
    assert response.status_code == 422
    assert response.json() == {
        'detail': [{
            'loc': ['body', 'country_code'],
            'msg': 'field required',
            'type': 'value_error.missing'
        }]
    }
PercySherlock
10 августа 2021 в 21:06
0

Спасибо за этот подробный ответ. Я определенно многому научился.