Spec-Zone.ru › Flask 2.3

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

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

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

$ pip install pytest

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

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

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

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

Фикстуры

Pytest фи́кстуры позволяют писать фрагменты кода, которые можно повторно использовать во всех тестах. Простая фикстура возвращает значение, но фикстура также может выполнять настройку, возвращать значение, а затем выполнять завершение. Фикстуры для приложения, тестового клиента и исполнителя командной строки показаны ниже, их можно разместить в 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"

Запуск команд с помощью исполнителя командной строки

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"

© 2007–2022 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.3.x/testing/

Spec-Zone.ru

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