Python: pytest и функции контекстного процессора шаблонов Flask.

У нас есть несколько функциональных тестов 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 conftest.py
      • Приспособление app()
      • Приспособление test_client( app )
    • Модуль test_routes.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():
Enter fullscreen mode Выйти из полноэкранного режима

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 к экземпляру тестового приложения также удовлетворило.

Больше всего я надеюсь, что эта информация поможет кому-нибудь в дальнейшем. Надеюсь, вы найдете это полезным… и спасибо, что прочитали.

Оцените статью
devanswers.ru
Добавить комментарий