Тестирование 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_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.2.x/testing/