Spec-Zone.ru › Flask 2.1

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

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

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

$ pip install pytest

В учебнике показано, как написать тесты для 100% покрытия примера приложения Flask — блога 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, 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={"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.1.x/testing/

Spec-Zone.ru

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