[FEATURE] Allow executing custom code once per API operation in pytest plugin
Is your feature request related to a problem? Please describe.
Schemathesis integrates with pytest, and at the moment, there is no way to execute custom code before & after the test body exactly once.
It might help integrate with frameworks like allure where you can emit some meta-information during the test execution. However, it might not be clear that tests generated by schema.parametrize are Hypothesis tests and are executed a bit differently than usual tests.
The reported case boils down to:
import allure
...
@schema.parametrize()
def test_api(case):
allure.dynamic.title(case.method)
allure.dynamic.story(case.endpoint.path)
...
We need a way to execute allure.dynamic once per API operation, not per each generated case.
Describe the solution you'd like Add pytest hooks to execute code before & after a test generated by Schemathesis.
Example:
def pytest_schemathesis_before_call(endpoint):
...
def pytest_schemathesis_after_call(exception, case):
...
Additional context Originally requested here - https://github.com/allure-framework/allure2/issues/1883
@acqa
Добрый день! Предлагаю перенести дискуссию сюда, т.к. на мой взгляд решению проблемы больше могут помочь именно изменения на стороне Schemathesis.
Как видно из скринов, если использовать антотацию @allure.story("Static story") над тестовой функцией, то проблем с множественным выполнением allure.dynamic.title не возникает - отчет формируется без дубликатов тестов (title-ов). А если использовать allure.dynamic.story() вместе с allure.dynamic.title в самой тестовой функции, то получаем дубликаты строк title-ов в отчете. Поэтому предположил, что ошибка на стороне формирователя результата allure.
Справедливо! тут наверное еще играет роль то, что Schemathesis довольно много делает под капотом и не совсем явно говорит об этом.
Если я правильно понял, то там речь про глубокую интеграцию hypothesis c pytest- ом. В моем примере, assert-ов для pytest-а нет совсем, я полагаюсь только на ошибки, которые выведет сам hypothesis.
Ключевой момент заключается в том что pytest не знает о том что Hypothesis запускает тестовую функцию N раз и считает это одним тестом. Т.е. всё то что внутри фунции test_api выполняется множество раз и pytest всё это считает одним тестом (если упростить, то внутри Hypothesis есть цикл который запускает test_api с разными case).
Поэтому получается что любые вызовы allure внутри теста будут производиться множество раз и встроенного способа запустить какой-то такой код нет. В принципе можно создать отдельную функцию которая бы запускалась один раз на тест, но тогда нужно четко определить условие на каком именно тесте её запускать. Если на первом то может быть чтото такое:
import functools
import os
import allure
import schemathesis
def call_once(func):
seen = set()
@functools.wraps(func)
def wrapper(*args, **kwargs):
test_name = os.environ.get("PYTEST_CURRENT_TEST")
if test_name in seen:
return
seen.add(test_name)
return func(*args, **kwargs)
return wrapper
@call_once
def title(value):
allure.dynamic.title(value)
@call_once
def story(value):
allure.dynamic.story(value)
schema = schemathesis.from_uri("http://0.0.0.0:8081/schema.yaml", validate_schema=False)
@schema.parametrize()
def test_api(case):
title(case.method)
story(case.endpoint.path)
with allure.step(
f"Endpoint: {case.formatted_path}; Query: {case.query}; Body: {case.body}"
):
resp = case.call()
resp_to_attach = f"{resp.request.method}\n{resp.request.url}\n{resp.status_code}\n{resp.text}"
allure.attach(resp_to_attach, name="output", attachment_type=allure.attachment_type.TEXT)
case.validate_response(resp)
Тут, конечно, не учитывается что функции могут чтото важное возвращать и тд, но локально у меня собирает данные без дублирования. Дайте знать если такой вариант работает в вашей ситуации :)
Но я посмотрю что можно сделать чтобы обойтись без таких функции.
В принципе можно создать отдельную функцию которая бы запускалась один раз на тест, но тогда нужно четко определить условие на каком именно тесте её запускать. Если на первом то может быть чтото такое:
Думаю, смена story и title на каждом первом тесте для определенного эндпойнта, хороший вариаент.
Тут, конечно, не учитывается что функции могут чтото важное возвращать и тд, но локально у меня собирает данные без дублирования. Дайте знать если такой вариант работает в вашей ситуации :)
Для меня работает ожидаемо. Спасибо!

Но я посмотрю что можно сделать чтобы обойтись без таких функции.
Ага, было бы удобно, если их, к примеру, упрятать под капот schema, чтобы в тестовой функции можно было обращаться через
schema.title(case.method)
schema. story(case.method)
Плюс, при реализации, было бы полезным, получить обертки над остальными методами allure.dynamic в schema.
Ага, было бы удобно, если их, к примеру, упрятать под капот schema, чтобы в тестовой функции можно было обращаться через
Если так сделать, то это будет значить что в schemathesis должна быть интегрированная поддержка pytest-allure (т.к. будет использоваться код из allure), а это добавит сложности в поддержке (нужно будет учитывать изменения в их API и отдельно тестировать новые версии, в которых обратная совместимость ломается), поэтому я бы предпочел это оставить на стороне пользователя. По крайней мере на данный момент, возможно в перспективе можно и более тесную интеграцию прикрутить, но пока рано об этом говорить.
В целом, если текущий вариант с теми функциями работает, то я отложу реализацию на январь :) дам знать когда будет готово