Тестирование приложений 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", follow_redirects=True)
# 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/stable/testing/