Тестирование приложений 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/