committed by
GitHub
2 changed files with 213 additions and 0 deletions
@ -0,0 +1,212 @@ |
|||||
|
# Тестирование |
||||
|
|
||||
|
Благодаря <a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette</a>, тестировать приложения **FastAPI** легко и приятно. |
||||
|
|
||||
|
Тестирование основано на библиотеке <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, которая в свою очередь основана на библиотеке Requests, так что все действия знакомы и интуитивно понятны. |
||||
|
|
||||
|
Используя эти инструменты, Вы можете напрямую задействовать <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a> с **FastAPI**. |
||||
|
|
||||
|
## Использование класса `TestClient` |
||||
|
|
||||
|
!!! info "Информация" |
||||
|
Для использования класса `TestClient` необходимо установить библиотеку <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>. |
||||
|
|
||||
|
Например, так: `pip install httpx`. |
||||
|
|
||||
|
Импортируйте `TestClient`. |
||||
|
|
||||
|
Создайте объект `TestClient`, передав ему в качестве параметра Ваше приложение **FastAPI**. |
||||
|
|
||||
|
Создайте функцию, название которой должно начинаться с `test_` (это стандарт из соглашений `pytest`). |
||||
|
|
||||
|
Используйте объект `TestClient` так же, как Вы используете `httpx`. |
||||
|
|
||||
|
Напишите простое утверждение с `assert` дабы проверить истинность Python-выражения (это тоже стандарт `pytest`). |
||||
|
|
||||
|
```Python hl_lines="2 12 15-18" |
||||
|
{!../../../docs_src/app_testing/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! tip "Подсказка" |
||||
|
Обратите внимание, что тестирующая функция является обычной `def`, а не асинхронной `async def`. |
||||
|
|
||||
|
И вызов клиента также осуществляется без `await`. |
||||
|
|
||||
|
Это позволяет вам использовать `pytest` без лишних усложнений. |
||||
|
|
||||
|
!!! note "Технические детали" |
||||
|
Также можно написать `from starlette.testclient import TestClient`. |
||||
|
|
||||
|
**FastAPI** предоставляет тот же самый `starlette.testclient` как `fastapi.testclient`. Это всего лишь небольшое удобство для Вас, как разработчика. |
||||
|
|
||||
|
!!! tip "Подсказка" |
||||
|
Если для тестирования Вам, помимо запросов к приложению FastAPI, необходимо вызывать асинхронные функции (например, для подключения к базе данных с помощью асинхронного драйвера), то ознакомьтесь со страницей [Асинхронное тестирование](../advanced/async-tests.md){.internal-link target=_blank} в расширенном руководстве. |
||||
|
|
||||
|
## Разделение тестов и приложения |
||||
|
|
||||
|
В реальном приложении Вы, вероятно, разместите тесты в отдельном файле. |
||||
|
|
||||
|
Кроме того, Ваше приложение **FastAPI** может состоять из нескольких файлов, модулей и т.п. |
||||
|
|
||||
|
### Файл приложения **FastAPI** |
||||
|
|
||||
|
Допустим, структура файлов Вашего приложения похожа на ту, что описана на странице [Более крупные приложения](./bigger-applications.md){.internal-link target=_blank}: |
||||
|
|
||||
|
``` |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ └── main.py |
||||
|
``` |
||||
|
|
||||
|
Здесь файл `main.py` является "точкой входа" в Ваше приложение и содержит инициализацию Вашего приложения **FastAPI**: |
||||
|
|
||||
|
|
||||
|
```Python |
||||
|
{!../../../docs_src/app_testing/main.py!} |
||||
|
``` |
||||
|
|
||||
|
### Файл тестов |
||||
|
|
||||
|
Также у Вас может быть файл `test_main.py` содержащий тесты. Можно разместить тестовый файл и файл приложения в одной директории (в директориях для Python-кода желательно размещать и файл `__init__.py`): |
||||
|
|
||||
|
``` hl_lines="5" |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ ├── main.py |
||||
|
│ └── test_main.py |
||||
|
``` |
||||
|
|
||||
|
Так как оба файла находятся в одной директории, для импорта объекта приложения из файла `main` в файл `test_main` Вы можете использовать относительный импорт: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../../docs_src/app_testing/test_main.py!} |
||||
|
``` |
||||
|
|
||||
|
...и писать дальше тесты, как и раньше. |
||||
|
|
||||
|
## Тестирование: расширенный пример |
||||
|
|
||||
|
Теперь давайте расширим наш пример и добавим деталей, чтоб посмотреть, как тестировать различные части приложения. |
||||
|
|
||||
|
### Расширенный файл приложения **FastAPI** |
||||
|
|
||||
|
Мы продолжим работу с той же файловой структурой, что и ранее: |
||||
|
|
||||
|
``` |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ ├── main.py |
||||
|
│ └── test_main.py |
||||
|
``` |
||||
|
|
||||
|
Предположим, что в файле `main.py` с приложением **FastAPI** есть несколько **операций пути**. |
||||
|
|
||||
|
В нём описана операция `GET`, которая может вернуть ошибку. |
||||
|
|
||||
|
Ещё есть операция `POST` и она тоже может вернуть ошибку. |
||||
|
|
||||
|
Обе *операции пути* требуют наличия в запросе заголовка `X-Token`. |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b_an_py310/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b_an_py39/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b_an/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.10+ без Annotated" |
||||
|
|
||||
|
!!! tip "Подсказка" |
||||
|
По возможности используйте версию с `Annotated`. |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b_py310/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+ без Annotated" |
||||
|
|
||||
|
!!! tip "Подсказка" |
||||
|
По возможности используйте версию с `Annotated`. |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b/main.py!} |
||||
|
``` |
||||
|
|
||||
|
### Расширенный файл тестов |
||||
|
|
||||
|
Теперь обновим файл `test_main.py`, добавив в него тестов: |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b/test_main.py!} |
||||
|
``` |
||||
|
|
||||
|
Если Вы не знаете, как передать информацию в запросе, можете воспользоваться поисковиком (погуглить) и задать вопрос: "Как передать информацию в запросе с помощью `httpx`", можно даже спросить: "Как передать информацию в запросе с помощью `requests`", поскольку дизайн HTTPX основан на дизайне Requests. |
||||
|
|
||||
|
Затем Вы просто применяете найденные ответы в тестах. |
||||
|
|
||||
|
Например: |
||||
|
|
||||
|
* Передаёте *path*-параметры или *query*-параметры, вписав их непосредственно в строку URL. |
||||
|
* Передаёте JSON в теле запроса, передав Python-объект (например: `dict`) через именованный параметр `json`. |
||||
|
* Если же Вам необходимо отправить *форму с данными* вместо JSON, то используйте параметр `data` вместо `json`. |
||||
|
* Для передачи *заголовков*, передайте объект `dict` через параметр `headers`. |
||||
|
* Для передачи *cookies* также передайте `dict`, но через параметр `cookies`. |
||||
|
|
||||
|
Для получения дополнительной информации о передаче данных на бэкенд с помощью `httpx` или `TestClient` ознакомьтесь с <a href="https://www.python-httpx.org" class="external-link" target="_blank">документацией HTTPX</a>. |
||||
|
|
||||
|
!!! info "Информация" |
||||
|
Обратите внимание, что `TestClient` принимает данные, которые можно конвертировать в JSON, но не модели Pydantic. |
||||
|
|
||||
|
Если в Ваших тестах есть модели Pydantic и Вы хотите отправить их в тестируемое приложение, то можете использовать функцию `jsonable_encoder`, описанную на странице [Кодировщик совместимый с JSON](encoder.md){.internal-link target=_blank}. |
||||
|
|
||||
|
## Запуск тестов |
||||
|
|
||||
|
Далее Вам нужно установить `pytest`: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install pytest |
||||
|
|
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
Он автоматически найдёт все файлы и тесты, выполнит их и предоставит Вам отчёт о результатах тестирования. |
||||
|
|
||||
|
Запустите тесты командой `pytest` и увидите результат: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pytest |
||||
|
|
||||
|
================ test session starts ================ |
||||
|
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 |
||||
|
rootdir: /home/user/code/superawesome-cli/app |
||||
|
plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 |
||||
|
collected 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> |
||||
|
``` |
||||
|
|
||||
|
</div> |
Loading…
Reference in new issue