Movatterモバイル変換


[0]ホーム

URL:


Перейти к содержанию
Join theFastAPI Cloud waiting list 🚀
Follow@fastapi onX (Twitter) to stay updated
FollowFastAPI onLinkedIn to stay updated
Subscribe to theFastAPI and friends newsletter 🎉
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor

Тестирование

🌐 Перевод выполнен с помощью ИИ и людей

Этот перевод был сделан ИИ под руководством людей. 🤝

В нем могут быть ошибки из-за неправильного понимания оригинального смысла или неестественности и т. д. 🤖

Вы можете улучшить этот перевод,помогая нам лучше направлять ИИ LLM.

Английская версия

БлагодаряStarlette, тестировать приложенияFastAPI легко и приятно.

Тестирование основано на библиотекеHTTPX, которая в свою очередь основана на библиотеке Requests, так что все действия знакомы и интуитивно понятны.

Используя эти инструменты, Вы можете напрямую задействоватьpytest сFastAPI.

Использование классаTestClient

Информация

Для использования классаTestClient сначала установитеhttpx.

Убедитесь, что Вы создаливиртуальное окружение, активировали его, а затем установили пакет, например:

$pipinstallhttpx

ИмпортируйтеTestClient.

Создайте объектTestClient, передав ему в качестве параметра Ваше приложениеFastAPI.

Создайте функцию, название которой должно начинаться сtest_ (это стандарт из соглашенийpytest).

Используйте объектTestClient так же, как Вы используетеhttpx.

Напишите простое утверждение сassert дабы проверить истинность Python-выражения (это тоже стандартpytest).

fromfastapiimportFastAPIfromfastapi.testclientimportTestClientapp=FastAPI()@app.get("/")asyncdefread_main():return{"msg":"Hello World"}client=TestClient(app)deftest_read_main():response=client.get("/")assertresponse.status_code==200assertresponse.json()=={"msg":"Hello World"}

Подсказка

Обратите внимание, что тестирующая функция является обычнойdef, а не асинхроннойasync def.

И вызов клиента также осуществляется безawait.

Это позволяет вам использоватьpytest без лишних усложнений.

Технические детали

Также можно написатьfrom starlette.testclient import TestClient.

FastAPI предоставляет тот же самыйstarlette.testclient какfastapi.testclient. Это всего лишь небольшое удобство для Вас, как разработчика. Но он берётся напрямую из Starlette.

Подсказка

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

Разделение тестов

В реальном приложении Вы, вероятно, разместите тесты в отдельном файле.

Кроме того, Ваше приложениеFastAPI может состоять из нескольких файлов, модулей и т.п.

Файл приложенияFastAPI

Допустим, структура файлов Вашего приложения похожа на ту, что описана на страницеБолее крупные приложения:

.├── app│   ├── __init__.py│   └── main.py

В файлеmain.py находится Ваше приложениеFastAPI:

fromfastapiimportFastAPIapp=FastAPI()@app.get("/")asyncdefread_main():return{"msg":"Hello World"}

Файл тестов

Также у Вас может быть файлtest_main.py содержащий тесты. Можно разместить тестовый файл и файл приложения в одной директории (в директориях для Python-кода желательно размещать и файл__init__.py):

.├── app│   ├── __init__.py│   ├── main.py│   └── test_main.py

Так как оба файла находятся в одной директории, для импорта объекта приложения из файлаmain в файлtest_main Вы можете использовать относительный импорт:

fromfastapi.testclientimportTestClientfrom.mainimportappclient=TestClient(app)deftest_read_main():response=client.get("/")assertresponse.status_code==200assertresponse.json()=={"msg":"Hello World"}

...и писать дальше тесты, как и раньше.

Тестирование: расширенный пример

Теперь давайте расширим наш пример и добавим деталей, чтоб посмотреть, как тестировать различные части приложения.

Расширенный файл приложенияFastAPI

Мы продолжим работу с той же файловой структурой, что и ранее:

.├── app│   ├── __init__.py│   ├── main.py│   └── test_main.py

Предположим, что в файлеmain.py с приложениемFastAPI есть несколькоопераций пути.

В нём описана операцияGET, которая может вернуть ошибку.

Ещё есть операцияPOST, и она может вернуть несколько ошибок.

Обеоперации пути требуют наличия в запросе HTTP-заголовкаX-Token.

fromtypingimportAnnotatedfromfastapiimportFastAPI,Header,HTTPExceptionfrompydanticimportBaseModelfake_secret_token="coneofsilence"fake_db={"foo":{"id":"foo","title":"Foo","description":"There goes my hero"},"bar":{"id":"bar","title":"Bar","description":"The bartenders"},}app=FastAPI()classItem(BaseModel):id:strtitle:strdescription:str|None=None@app.get("/items/{item_id}",response_model=Item)asyncdefread_main(item_id:str,x_token:Annotated[str,Header()]):ifx_token!=fake_secret_token:raiseHTTPException(status_code=400,detail="Invalid X-Token header")ifitem_idnotinfake_db:raiseHTTPException(status_code=404,detail="Item not found")returnfake_db[item_id]@app.post("/items/")asyncdefcreate_item(item:Item,x_token:Annotated[str,Header()])->Item:ifx_token!=fake_secret_token:raiseHTTPException(status_code=400,detail="Invalid X-Token header")ifitem.idinfake_db:raiseHTTPException(status_code=409,detail="Item already exists")fake_db[item.id]=item.model_dump()returnitem
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfastapiimportFastAPI,Header,HTTPExceptionfrompydanticimportBaseModelfake_secret_token="coneofsilence"fake_db={"foo":{"id":"foo","title":"Foo","description":"There goes my hero"},"bar":{"id":"bar","title":"Bar","description":"The bartenders"},}app=FastAPI()classItem(BaseModel):id:strtitle:strdescription:str|None=None@app.get("/items/{item_id}",response_model=Item)asyncdefread_main(item_id:str,x_token:str=Header()):ifx_token!=fake_secret_token:raiseHTTPException(status_code=400,detail="Invalid X-Token header")ifitem_idnotinfake_db:raiseHTTPException(status_code=404,detail="Item not found")returnfake_db[item_id]@app.post("/items/")asyncdefcreate_item(item:Item,x_token:str=Header())->Item:ifx_token!=fake_secret_token:raiseHTTPException(status_code=400,detail="Invalid X-Token header")ifitem.idinfake_db:raiseHTTPException(status_code=409,detail="Item already exists")fake_db[item.id]=item.model_dump()returnitem

Расширенный файл тестов

Теперь обновим файлtest_main.py, добавив в него тестов:

fromfastapi.testclientimportTestClientfrom.mainimportappclient=TestClient(app)deftest_read_item():response=client.get("/items/foo",headers={"X-Token":"coneofsilence"})assertresponse.status_code==200assertresponse.json()=={"id":"foo","title":"Foo","description":"There goes my hero",}deftest_read_item_bad_token():response=client.get("/items/foo",headers={"X-Token":"hailhydra"})assertresponse.status_code==400assertresponse.json()=={"detail":"Invalid X-Token header"}deftest_read_nonexistent_item():response=client.get("/items/baz",headers={"X-Token":"coneofsilence"})assertresponse.status_code==404assertresponse.json()=={"detail":"Item not found"}deftest_create_item():response=client.post("/items/",headers={"X-Token":"coneofsilence"},json={"id":"foobar","title":"Foo Bar","description":"The Foo Barters"},)assertresponse.status_code==200assertresponse.json()=={"id":"foobar","title":"Foo Bar","description":"The Foo Barters",}deftest_create_item_bad_token():response=client.post("/items/",headers={"X-Token":"hailhydra"},json={"id":"bazz","title":"Bazz","description":"Drop the bazz"},)assertresponse.status_code==400assertresponse.json()=={"detail":"Invalid X-Token header"}deftest_create_existing_item():response=client.post("/items/",headers={"X-Token":"coneofsilence"},json={"id":"foo","title":"The Foo ID Stealers","description":"There goes my stealer",},)assertresponse.status_code==409assertresponse.json()=={"detail":"Item already exists"}
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfastapi.testclientimportTestClientfrom.mainimportappclient=TestClient(app)deftest_read_item():response=client.get("/items/foo",headers={"X-Token":"coneofsilence"})assertresponse.status_code==200assertresponse.json()=={"id":"foo","title":"Foo","description":"There goes my hero",}deftest_read_item_bad_token():response=client.get("/items/foo",headers={"X-Token":"hailhydra"})assertresponse.status_code==400assertresponse.json()=={"detail":"Invalid X-Token header"}deftest_read_nonexistent_item():response=client.get("/items/baz",headers={"X-Token":"coneofsilence"})assertresponse.status_code==404assertresponse.json()=={"detail":"Item not found"}deftest_create_item():response=client.post("/items/",headers={"X-Token":"coneofsilence"},json={"id":"foobar","title":"Foo Bar","description":"The Foo Barters"},)assertresponse.status_code==200assertresponse.json()=={"id":"foobar","title":"Foo Bar","description":"The Foo Barters",}deftest_create_item_bad_token():response=client.post("/items/",headers={"X-Token":"hailhydra"},json={"id":"bazz","title":"Bazz","description":"Drop the bazz"},)assertresponse.status_code==400assertresponse.json()=={"detail":"Invalid X-Token header"}deftest_create_existing_item():response=client.post("/items/",headers={"X-Token":"coneofsilence"},json={"id":"foo","title":"The Foo ID Stealers","description":"There goes my stealer",},)assertresponse.status_code==409assertresponse.json()=={"detail":"Item already exists"}

Если Вы не знаете, как передать информацию в запросе, можете воспользоваться поисковиком (погуглить) и задать вопрос: "Как передать информацию в запросе с помощьюhttpx", можно даже спросить: "Как передать информацию в запросе с помощьюrequests", поскольку дизайн HTTPX основан на дизайне Requests.

Затем Вы просто применяете найденные ответы в тестах.

Например:

  • Передаётеpath-параметры илиquery-параметры, вписав их непосредственно в строку URL.
  • Передаёте JSON в теле запроса, передав Python-объект (например:dict) через именованный параметрjson.
  • Если же Вам необходимо отправитьформу с данными вместо JSON, то используйте параметрdata вместоjson.
  • Для передачиHTTP-заголовков, передайте объектdict через параметрheaders.
  • Для передачиcookies также передайтеdict, но через параметрcookies.

Для получения дополнительной информации о передаче данных на бэкенд с помощьюhttpx илиTestClient ознакомьтесь сдокументацией HTTPX.

Информация

Обратите внимание, чтоTestClient принимает данные, которые можно конвертировать в JSON, но не модели Pydantic.

Если в Ваших тестах есть модели Pydantic и Вы хотите отправить их в тестируемое приложение, то можете использовать функциюjsonable_encoder, описанную на страницеКодировщик совместимый с JSON.

Запуск

Далее Вам нужно установитьpytest.

Убедитесь, что Вы создаливиртуальное окружение, активировали его, а затем установили пакет, например:

$pipinstallpytest---> 100%

Он автоматически найдёт все файлы и тесты, выполнит их и предоставит Вам отчёт о результатах тестирования.

Запустите тесты:

$pytest================ test session starts ================platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1rootdir: /home/user/code/superawesome-cli/appplugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1collected 6 items---> 100%test_main.py <span style="color: green; white-space: pre;">......                            [100%]</span><span style="color: green;">================= 1 passed in 0.03s =================</span>

[8]ページ先頭

©2009-2026 Movatter.jp