Spec-Zone.ru › Flask 3.0

Тестирование приложений Flask

Flask предоставляет утилиты для тестирования приложения. Данное руководство описывает методы работы с разными частями приложения в тестах.

Мы будем использовать фреймворк pytest для настройки и запуска наших тестов.

$ pip install pytest

В руководстве описано, как написать тесты для 100% покрытия примера приложения Flaskr blog. Подробное описание конкретных тестов для приложения см. в руководстве по тестам.

Идентификация тестов

Тесты обычно находятся в папке tests. Тесты представляют собой функции, начинающиеся с test_, в Python-модулях, начинающихся с test_. Тесты также могут быть сгруппированы в классах, начинающихся с Test.

Иногда сложно понять, что следует тестировать. Обычно старайтесь тестировать написанный вами код, а не код используемых библиотек, так как они уже протестированы. Старайтесь выделять сложные поведения в отдельные функции для их индивидуального тестирования.

Фикстуры

Pytest фикстуры позволяют писать фрагменты кода, которые можно повторно использовать в различных тестах. Простая фикстура возвращает значение, но фикстура также может выполнять настройку, возвращать значение, а затем выполнять завершение. Примеры фикстур для приложения, тестового клиента и запуска CLI показаны ниже; их можно разместить в tests/conftest.py.

Если вы используете фабрику приложения фабрику приложения, определите фикстуру app для создания и настройки экземпляра приложения. Вы можете добавить код перед и после yield для настройки и завершения других ресурсов, таких как создание и очистка базы данных.

Если вы не используете фабрику, у вас уже есть объект приложения, который вы можете импортировать и настроить напрямую. Вы по-прежнему можете использовать фикстуру app для настройки и завершения ресурсов.

import pytest
from my_project import create_app

@pytest.fixture()
def app():
    app = create_app()
    app.config.update({
        "TESTING": True,
    })

    # other setup can go here

    yield app

    # clean up / reset resources here


@pytest.fixture()
def client(app):
    return app.test_client()


@pytest.fixture()
def runner(app):
    return app.test_cli_runner()

Отправка запросов с помощью тестового клиента

Тестовый клиент отправляет запросы к приложению без запуска активного сервера. Клиент Flask расширяет клиент Werkzeug, см. эти документы для дополнительной информации.

У client есть методы, соответствующие общим методам HTTP-запросов, например, client.get() и client.post(). Они принимают множество аргументов для создания запроса; полную документацию можно найти в EnvironBuilder. Обычно вы будете использовать path, query_string, headers, и data или json.

Для отправки запроса вызовите метод, который должен использовать запрос, с путем к маршруту для тестирования. Возвращается TestResponse, чтобы проверить данные ответа. Он имеет все обычные свойства объекта ответа. Обычно вы будете обращаться к response.data, который представляет собой байты, возвращенные представлением. Если вам нужно использовать текст, Werkzeug 2.1 предоставляет response.text, или используйте response.get_data(as_text=True).

def test_request_example(client):
    response = client.get("/posts")
    assert b"<h2>Hello, World!</h2>" in response.data

Передайте словарь query_string={"key": "value", ...} для установки аргументов в строке запроса (после ? в URL). Передайте словарь headers={} для установки заголовков запроса.

Для отправки тела запроса в запросах POST или PUT передайте значение data. Если передаются необработанные байты, то используется именно это тело. Обычно вы передаете словарь для установки данных формы.

Данные формы

Для отправки данных формы передайте словарь в data. Заголовок Content-Type будет автоматически установлен в multipart/form-data или application/x-www-form-urlencoded.

Если значение является объектом файла, открытым для чтения байтов (режим "rb"), он будет обрабатываться как загруженный файл. Чтобы изменить обнаруженное имя файла и тип содержимого, передайте кортеж (file, filename, content_type). Объекты файлов будут закрыты после отправки запроса, поэтому они не нуждаются в использовании обычного шаблона with open() as f:.

Полезно хранить файлы в папке tests/resources, а затем использовать pathlib.Path для получения файлов относительно текущего тестового файла.

from pathlib import Path

# get the resources folder in the tests folder
resources = Path(__file__).parent / "resources"

def test_edit_user(client):
    response = client.post("/user/2/edit", data={
        "name": "Flask",
        "theme": "dark",
        "picture": (resources / "picture.png").open("rb"),
    })
    assert response.status_code == 200

JSON-данные

Для отправки JSON-данных передайте объект в json. Заголовок Content-Type будет автоматически установлен в application/json.

Аналогично, если ответ содержит JSON-данные, атрибут response.json будет содержать десериализованный объект.

def test_json_data(client):
    response = client.post("/graphql", json={
        "query": """
            query User($id: String!) {
                user(id: $id) {
                    name
                    theme
                    picture_url
                }
            }
        """,
        variables={"id": 2},
    })
    assert response.json["data"]["user"]["name"] == "Flask"

Следование перенаправлениям

По умолчанию клиент не отправляет дополнительные запросы, если ответ является перенаправлением. Передав follow_redirects=True в метод запроса, клиент будет продолжать отправлять запросы до тех пор, пока не вернётся ответ, не являющийся перенаправлением.

TestResponse.history — кортеж ответов, которые привели к конечному ответу. Каждый ответ имеет атрибут request, который записывает запрос, породивший этот ответ.

def test_logout_redirect(client):
    response = client.get("/logout")
    # Check that there was one redirect response.
    assert len(response.history) == 1
    # Check that the second request was to the index page.
    assert response.request.path == "/index"

Доступ и изменение сессии

Для доступа к переменным контекста Flask, в основном к session, используйте клиента в блоке with. Контекст приложения и запроса останутся активными после отправки запроса, до тех пор, пока не завершится блок with.

from flask import session

def test_access_session(client):
    with client:
        client.post("/auth/login", data={"username": "flask"})
        # session is still accessible
        assert session["user_id"] == 1

    # session is no longer accessible

Если вам нужно получить доступ к или установить значение в сессии до отправки запроса, используйте метод клиента session_transaction() в блоке with. Он возвращает объект сессии и сохранит сессию после завершения блока.

from flask import session

def test_modify_session(client):
    with client.session_transaction() as session:
        # set a user id without going through the login route
        session["user_id"] = 1

    # session is saved now

    response = client.get("/users/me")
    assert response.json["username"] == "flask"

Запуск команд с помощью CLI-раннера

Flask предоставляет test_cli_runner() для создания FlaskCliRunner, который запускает CLI-команды изолированно и захватывает вывод в объекте Result. Раннер Flask расширяет раннер Click, см. эти документы для дополнительной информации.

Используйте метод раннера invoke() для вызова команд аналогично тому, как они вызываются командой flask из командной строки.

import click

@app.cli.command("hello")
@click.option("--name", default="World")
def hello_command(name):
    click.echo(f"Hello, {name}!")

def test_hello_command(runner):
    result = runner.invoke(args="hello")
    assert "World" in result.output

    result = runner.invoke(args=["hello", "--name", "Flask"])
    assert "Flask" in result.output

Тесты, зависящие от активного контекста

У вас могут быть функции, вызываемые из представлений или команд, которые ожидают активного контекста приложения или запроса, так как они обращаются к request, session, или current_app. Вместо тестирования их отправкой запроса или вызовом команды вы можете создать и активировать контекст напрямую.

Используйте with app.app_context() для добавления контекста приложения. Например, расширения баз данных обычно требуют активного контекста приложения для выполнения запросов.

def test_db_post_model(app):
    with app.app_context():
        post = db.session.query(Post).get(1)

Используйте with app.test_request_context() для добавления контекста запроса. Он принимает те же аргументы, что и методы запроса тестового клиента.

def test_validate_user_edit(app):
    with app.test_request_context(
        "/user/2/edit", method="POST", data={"name": ""}
    ):
        # call a function that accesses `request`
        messages = validate_edit_user()

    assert messages["name"][0] == "Name cannot be empty."

Создание тестового контекста запроса не запускает код обработки Flask, поэтому before_request функции не вызываются. Если вам нужно их вызвать, обычно лучше отправить полный запрос. Однако их можно вызвать вручную.

def test_auth_token(app):
    with app.test_request_context("/user/2/edit", headers={"X-Auth-Token": "1"}):
        app.preprocess_request()
        assert g.user.name == "Flask"

© 2010 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/3.0.x/testing/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API