У нас есть несколько функциональных тестов pytest, например, тесты, которые получают содержимое HTML. Для генерации этого HTML-содержимого используются некоторые функции контекстного_процессора шаблона Flask. Эти функции доступны экземпляру приложения Flask, который создается с помощью шаблона Application Factory. Как сделать эти же функции context_processor доступными в экземпляре приложения pytest, который также создается с помощью шаблона Application Factory? Мы обсуждаем этот вопрос, а также pytest application fixture и test client fixure.
Мне нужно было написать несколько методов pytest, которые делают запросы к маршрутам. Некоторые из этих запросов возвращают содержимое HTML. Для генерации этого содержимого используются функции контекста_процессора шаблона Flask. То есть функции, которые украшены:
@app.context_processor
Мои тесты провалились, так как я не сделал эти функции доступными для фикстуры приложения pytest. Я искал решения, но не смог найти ни одного… Я протестировал то, что, по моему мнению, может сработать, и оно сработало.
Короче говоря, функция приспособления приложения в модуле pytest conftest.py должна украсить экземпляр приложения pytest теми же функциями контекста_процессора шаблона Flask. Например:
File D:project_nametestsconftest.py
@pytest.fixture(scope='module')
def app():
...
app = create_app()
...
app.app_context().push()
"""
Making all custom template functions available
to the test application instance.
"""
from project_name.utils import context_processor
return app
По сути, мы создаем тестовый экземпляр приложения, используя функцию шаблона Application Factory, как и реальный экземпляр приложения.
app.app_context().push()
Приведенная выше строка создает действительный контекст для экземпляра тестового приложения. Без действительного контекста мы получим сообщение об ошибке работы вне контекста приложения. Похоже, что с app.app_context().push() мы должны вызвать только один раз, тогда контекст будет доступен во всем приложении, тогда как с app.app_context():, контекст доступен только в области видимости with:.
Затем вызов импорта
from project_name.utils import context_processor
украшает экземпляр тестового приложения всеми функциями контекста_процессора шаблона Flask, реализованными в модуле:
D:project_namesrcproject_nameutilscontext_processor.py
Вот и вся суть… Я демонстрирую это с помощью соответствующего проекта и тестов в следующих разделах.
✿✿✿
- Оглавление
- Исходный код проекта
- Схема проекта после завершения
- Установка необходимых пакетов для pytest
- Шаблон echo.html и модуль context_processor.py
- Шаблон echo.html
- Модуль context_processor.py
- Модуль точки входа в приложение app.py
- Коды контроллера
- Модуль контроллера __init__.py
- Модуль контроллера echo.py
- Модуль urls.py и фабричный шаблон __init__.py
- Тесты
- pytest entry module conftest.py
- Модуль test_routes.py
- Последняя версия Flask и файл .env
- Тесты Synology DS218
- Загрузка кодов
- Заключительные замечания
Оглавление
- Начальный код проекта
- Схема проекта после завершения
- Установка необходимых пакетов для pytest
- Шаблон echo.html и модуль context_processor.py
- Шаблон echo.html
- Модуль context_processor.py
- Модуль точки входа в приложение app.py
- Коды контроллера
- Модуль контроллера __init__.py
- Модуль echo.py контроллера
- Модуль urls.py и модуль фабрики шаблонов __init__.py
- Тесты
- модуль pytest entry conftest.py
- Приспособление app()
- Приспособление test_client( app )
- Модуль test_routes.py
- модуль pytest entry conftest.py
- Flask последней версии и .env файл
- Тесты Synology DS218
- Загрузка кодов
- Заключительные замечания
Исходный код проекта
Мы будем использовать существующий очень простой проект app_demo, который был создан для других предыдущих постов. Пожалуйста, получите его, используя:
git clone -b v1.0.0 https://github.com/behai-nguyen/app-demo.git
В нем есть только один маршрут: http://localhost:5000/ — который отображает Hello, World!
Вкратце, схема проекта такова:
D:app_demo
|
|-- .env
|-- app.py
|-- setup.py
|
|-- src
| |
| |-- app_demo
| |
| |-- __init__.py
| |-- config.py
|
|-- venv
Мы построим еще один маршрут /echo, используя Flask Blueprint, и напишем тесты для всех двух (2) маршрутов.
Схема проекта после завершения
На диаграмме ниже показана схема проекта после его завершения. Обратите внимание, что ★ обозначает новые файлы, а ☆ — файлы, которые нужно изменить:
D:app_demo
|
|-- .env ☆
|-- app.py ☆
|-- setup.py ☆
|-- pytest.ini ★
|
|-- src
| |
| |-- app_demo
| |
| |-- __init__.py ☆
| |-- config.py
| |-- urls.py ★
| |
| |-- controllers ★
| | |
| | |-- __init__.py
| | |-- echo.py
| |
| |-- utils ★
| | |
| | |-- __init__.py
| | |-- context_processor.py
| | |-- functions.py
| |
| |-- templates ★
| | |
| | |-- base_template.html
| | |-- echo
| | | |
| | | |--echo.html
|
|-- tests ★
| |
| |-- conftest.py
| |-- functional
| |
| |-- test_routes.py
|
|-- venv
Я протестировал этот проект на Synology DS218, DSM 7.1-42661 Update 3, под управлением Python 3.9 Beta; и Windows 10 Pro, версия 10.0.19044 build 19044, под управлением Python 3.10.1.
Готовые коды для этой заметки можно загрузить с помощью:
git clone -b v1.0.4 https://github.com/behai-nguyen/app-demo.git
Обратите внимание, что тег v1.0.4. Пожалуйста, игнорируйте все файлы, связанные с Docker.
Установка необходимых пакетов для pytest
Нам нужны пакеты pytest и coverage. Обновите setup.py, чтобы включить эти два пакета, затем установите проект в режиме редактирования:
(venv) D:app_demo>venvScriptspip.exe install -e .
(venv) behai@omphalos-nas-01:/volume1/web/app_demo$ sudo venv/bin/pip install -e .
Шаблон echo.html и модуль context_processor.py
Шаблон echo.html
Это шаблон echo.html во всей его полноте. Он довольно прост, его достаточно для демонстрации функции контекста_процессора шаблона Flask print_echo( request ):
{% set echo = print_echo( request ) %}
Мы сохраняем значение, возвращенное из print_echo( request ), в переменную шаблона echo. Затем мы просто выводим содержимое этой переменной. Если это POST-запрос, то мы выводим список пар ключ-значение, которые были отправлены. Строка «Дата-Время» нужна для того, чтобы содержимое HTML выглядело немного динамичным.
Для отправки POST-запросов на http://localhost:5000/echo я использую приложение The Postman App — во вкладке Body выберите x-www-form-urlencoded, а затем введите данные для отправки в предоставленный список. Нажмите кнопку Send (Отправить) — мы должны увидеть HTML-ответы, которые придут в разделе ответов ниже.
Модуль context_processor.py
Это модуль context_processor.py в полном объеме. В нем есть только одна простая функция. Я не думаю, что это потребует каких-либо объяснений. Ключевой момент, в моем понимании:
...
from flask import current_app as app
@app.context_processor
def print_echo():
def __print_echo( request ):
...
return data
return dict( print_echo=__print_echo )
Мы должны использовать current_app из Flask, так как мы украшаем функцию шаблона:
@app.context_processor
def print_echo():
current_app определяется как:
Прокси для приложения, обрабатывающего текущий запрос.
https://flask.palletsprojects.com/en/2.1.x/api/#flask.current_app
Это должно иметь смысл, так как экземпляр приложения может быть экземпляром веб-сервера разработки или экземпляром из pytest, как мы сейчас обсуждаем.
Мы понимаем, что это только демонстрационный метод, поэтому мы придумываем данные для этой цели. Для реальных приложений данные могут поступать из таких источников, как база данных, вычисляемые данные и т.д. Кроме того, мы можем иметь столько методов, сколько захотим.
Модуль точки входа в приложение app.py
Как упоминалось ранее, функции контекстного процессора должны быть доступны для текущего запущенного экземпляра приложения. Обновленный модуль точки входа в приложение app.py:
...
with app.app_context():
from app_demo.utils import context_processor
загружает функцию контекстного процессора, рассмотренную в модуле The context_processor.py, для только что созданного экземпляра приложения. Обратите внимание:
...
with app.app_context():
без вышеуказанного вызова приведет к RuntimeError: Работа вне контекста приложения. ошибка.
Коды контроллера
Модуль контроллера __init__.py
controllers_init_.py определяет экземпляр Flask Blueprint echo_blueprint.
Модуль контроллера echo.py
Модуль controllersecho.py содержит только одну однострочную функцию, которая просто рендерит и возвращает шаблон echo.html, рассмотренный в разделе Шаблон echo.html.
Модуль urls.py и фабричный шаблон __init__.py
app_demourls.py определяет список URL-мапперов и список доступных экземпляров Flask Blueprint.
Маршрут /echo поддерживает методы запроса GET и POST. Он сопоставлен с экземпляром echo_blueprint, рассмотренным в модуле Controller init.py, а методом ответа, который обслуживает HTML-контент, является метод do_echo(), рассмотренный в модуле Controller echo.py.
Модуль шаблона Application Factory app_demo_init_.py был обновлен для поддержки маршрута /echo. Изменения приведены ниже:
...
from app_demo.utils.functions import template_root_path
def create_app():
app = Flask( 'dsm-python-demo', template_folder=template_root_path() )
...
app.url_map.strict_slashes = False
...
register_blueprints( app )
...
def register_blueprints( app ):
...
Экземпляру приложения теперь присваивается template_folder. Отключение strict_slashes, чтобы сделать /echo и /echo/ одним и тем же маршрутом. И, наконец, вызовы новой функции register_blueprints для регистрации экземпляра(ов) Flask Blueprint и URL(ов), о которых говорилось выше.
Тесты
Это основная часть этого поста… Это займет некоторое время 😂.
pytest entry module conftest.py
Фикстура app()
Давайте посмотрим на tests/conftest.py:
@pytest.fixture(scope='module')
def app():
"""
Application fixure.
"""
app = create_app()
app.app_context().push()
"""
Making all custom template functions available
to the test application instance.
"""
from app_demo.utils import context_processor
return app
Приведенный выше метод создает экземпляр тестового приложения, используя тот же шаблон Application Factory, что и в самом приложении. Затем он создает действительный контекст для экземпляра тестового приложения с помощью вызова app.app_context().push(). Далее, что мы и пытались сделать — он загружает функцию контекстного процессора, рассмотренную в модуле The context_processor.py, для только что созданного экземпляра тестового приложения. Это точно так же, как и для собственно приложения, рассмотренного в модуле The application entry point module app.py.
Обратите внимание, что в этом посте ни один из тестов не использует этот метод напрямую, однако это будет структура тестов, которой я буду следовать в дальнейшем. В любом случае, он будет использоваться фикстурой test_client( app ), которую мы рассмотрим далее.
Приспособление test_client( app )
@pytest.fixture(scope='module')
def test_client( app ):
"""
Creates a test client.
app.test_client() is able to submit HTTP requests.
The app argument is the app() fixure above.
"""
with app.test_client() as testing_client:
yield testing_client # Return to caller.
Аргумент app — это фикстура app(), которая будет вызываться автоматически. Лично я воспринимаю app.test_client() как веб-браузер, мини-Постман и т.д., который позволяет нам делать HTTP-запросы.
Поскольку фикстура app() уже поставляется с контекстом через вызов app.app_context().push(), мы можем вызывать app.test_client() без результата в виде сообщения об ошибке при работе вне контекста приложения.
Модуль test_routes.py
Существует только один тестовый модуль — functionaltest_routes.py:
...
@pytest.mark.hello_world
def test_hello_world( test_client ):
...
@pytest.mark.echo
def test_echo_get_1( test_client ):
...
@pytest.mark.echo
def test_echo_get_2( test_client ):
...
@pytest.mark.echo
def test_echo_post( test_client ):
...
@pytest.mark.hello_world и @pytest.mark.echo являются необязательными — они позволяют нам запускать определенные тесты, а не все:
(venv) D:app_demo>venvScriptspytest.exe -m echo
(venv) D:app_demo>venvScriptspytest.exe -m hello_world
(venv) behai@omphalos-nas-01:/volume1/web/app_demo$ venv/bin/pytest -m echo
(venv) behai@omphalos-nas-01:/volume1/web/app_demo$ venv/bin/pytest -m hello_world
hello_world и echo определяются в конфигурационном файле pytest.ini.
Аргументом test_client для всех тестовых методов является фикс test_client( app ), рассмотренный ранее. Тестовые методы используют его методы get() и post() для выполнения запросов, а затем просматривают HTML-ответы в поисках определенных текстов, которые, как мы ожидали, должны быть в ответах.
Последняя версия Flask и файл .env
Я удалил Flask, чтобы установить последнюю версию. Последняя версия выдает следующее предупреждение:
'FLASK_ENV' is deprecated and will not be used in Flask 2.3. Use 'FLASK_DEBUG' instead.
Файл окружения .env был обновлен с FLASK_DEBUG=True, чтобы избавиться от предупреждения.
Тесты Synology DS218
Как уже упоминалось ранее, этот проект работает под Linux:
Загрузка кодов
Вкратце, коды для этого поста можно загрузить с помощью:
git clone -b v1.0.4 https://github.com/behai-nguyen/app-demo.git
Обратите внимание, что тег v1.0.4. Пожалуйста, игнорируйте все файлы, связанные с Docker.
Заключительные замечания
Мне очень понравилось работать над этим проектом. Особенно объяснять приспособление app() и приспособление test_client( app ) своими словами. Когда я впервые знакомился с pytest, эти два компонента показались мне немного сложными для понимания.
Успешное применение функций контекста_процессора шаблона Flask к экземпляру тестового приложения также удовлетворило.
Больше всего я надеюсь, что эта информация поможет кому-нибудь в дальнейшем. Надеюсь, вы найдете это полезным… и спасибо, что прочитали.