Контекст
Я начал использовать pytest-vcr
, который является pytest
оберткой плагина VCR.py
, который я задокументировал в этой записи блога по расширенному тестированию Python .
Записывает весь HTTP-трафик в cassettes/*.yml
файлы при первом запуске теста для сохранения снимков. Аналогично Jest тестирование снимков для веб-компонентов.
В последующих тестовых прогонах, если запрос искажен, он не найдет совпадение и выдает исключение, сообщающее, что запись новых запросов запрещена , и он не нашел существующую запись.
Вопрос
VCR.py
поднимает CannotOverwriteExistingCassetteException
, что не особенно информативно в отношении того, почему оно не соответствует.
Как использовать хуки pytest pytest_exception_interact
, чтобы заменить это исключение более информативным, используя информацию о приборах?
Я нырнул в свой site-packages
, где VCR.py
равен pip installed
, и переписал, как я хочу, чтобы он обрабатывал исключение. Мне просто нужно знать, как заставить этот хук pytest_exception_interact
работать правильно, чтобы получить доступ к приборам с этого тестового узла (до его очистки) и вызвать другое исключение.
Пример * * тысяча сорок-четырь
Позволяет получить зависимости.
$ pip install pytest pytest-vcr requests
test_example.py:
import pytest
import requests
@pytest.mark.vcr
def test_example():
r = requests.get("https://www.stackoverflow.com")
assert r.status_code == 200
$ pytest test_example.py --vcr-record=once
...
test_example.py::test_example PASSED
...
$ ls cassettes/
cassettes/test_example.yml
$ head cassettes/test_example.yml
interactions:
- request:
uri: https://wwwstackoverflow.com
body: null
headers:
Accept:
- '*/*'
$ pytest test_example.py --vcr-record=none
...
test_example.py::test_example PASSED
...
Теперь измените URI в тесте на "https://www.google.com":
test_example.py:
import pytest
import requests
@pytest.mark.vcr
def test_example():
r = requests.get("https://www.google.com")
assert r.status_code == 200
И снова запустите тест, чтобы обнаружить регрессию:
$ pytest test_example.py --vcr-record=none
E vcr.errors.CannotOverwriteExistingCassetteException: No match for the request (<Request (GET) https://www.google.com/>)
...
Я могу добавить conftest.py
файл в корень моей тестовой структуры, чтобы создать локальный плагин , и я могу проверить, что могу перехватить исключение и внедрить свой собственный, используя:
conftest.py
import pytest
from vcr.errors import CannotOverwriteExistingCassetteException
from vcr.config import VCR
from vcr.cassette import Cassette
class RequestNotFoundCassetteException(CannotOverwriteExistingCassetteException):
...
@pytest.fixture(autouse=True)
def _vcr_marker(request):
marker = request.node.get_closest_marker("vcr")
if marker:
cassette = request.getfixturevalue("vcr_cassette")
vcr = request.getfixturevalue("vcr")
request.node.__vcr_fixtures = dict(vcr_cassette=cassette, vcr=vcr)
yield
@pytest.hookimpl(hookwrapper=True)
def pytest_exception_interact(node, call, report):
excinfo = call.excinfo
if report.when == "call" and isinstance(excinfo.value, CannotOverwriteExistingCassetteException):
# Safely check for fixture pass through on this node
cassette = None
vcr = None
if hasattr(node, "__vcr_fixtures"):
for fixture_name, fx in node.__vcr_fixtures.items():
vcr = fx if isinstance(fx, VCR)
cassette = fx if isinstance(fx, Cassette)
# If we have the extra fixture context available...
if cassette and vcr:
match_properties = [f.__name__ for f in cassette._match_on]
cassette_reqs = cassette.requests
# filtered_req = cassette.filter_request(vcr._vcr_request)
# this_req, req_str = __format_near_match(filtered_req, cassette_reqs, match_properties)
# Raise and catch a new excpetion FROM existing one to keep the traceback
# https://stackoverflow.com/a/24752607/622276
# https://docs.python.org/3/library/exceptions.html#built-in-exceptions
try:
raise RequestNotFoundCassetteException(
f"\nMatching Properties: {match_properties}\n" f"Cassette Requests: {cassette_reqs}\n"
) from excinfo.value
except RequestNotFoundCassetteException as e:
excinfo._excinfo = (type(e), e)
report.longrepr = node.repr_failure(excinfo)
Это та часть, где документация в Интернете становится довольно тонкой.
Как получить доступ к устройству vcr_cassette
и вернуть другое исключение?
Я хочу получить filtered_request
, который пытался запросить, и список cassette_requests
и использование стандартной библиотеки Python difflib , чтобы получить дельты для информации, которая расходилась.
PyTest Code Spelunking
Внутренние компоненты для запуска одного теста с триггерами pytest pytest_runtest_protocol
, который эффективно выполняет следующие три call_and_report
вызова для получения коллекции отчетов.
SRC / _pytest / runner.py: L77-L94
def runtestprotocol(item, log=True, nextitem=None):
# Abbreviated
reports = []
reports.append(call_and_report(item, "setup", log))
reports.append(call_and_report(item, "call", log))
reports.append(call_and_report(item, "teardown", log))
return reports
Итак, я после изменения отчета на этапе call ... но до сих пор не знаю, как мне получить доступ к информации о приборе.
SRC / _pytest / runner.py: L166-L174
def call_and_report(item, when, log=True, **kwds):
call = call_runtest_hook(item, when, **kwds)
hook = item.ihook
report = hook.pytest_runtest_makereport(item=item, call=call)
if log:
hook.pytest_runtest_logreport(report=report)
if check_interactive_exception(call, report):
hook.pytest_exception_interact(node=item, call=call, report=report)
return report
Похоже, есть несколько вспомогательных методов для генерации нового ExceptionRepresentation, поэтому я обновил пример conftest.py .
ЦСИ / _pytest / reports.py: L361
longrepr = item.repr_failure(excinfo)
ОБНОВЛЕНИЕ # 1 2019-06-26 : Благодаря некоторым указателям от @ hoefling в комментариях я обновил свой conftest.py .
- Правильно повторно вызвать исключение, используя форму
raise ... from ...
.
- Переопределите
_vcr_marker
, чтобы прикрепить приборы vcr
и vcr_cassette
к request.node
, которые представляют этот отдельный тестовый элемент.
- Остальное : Получить доступ к перехваченному запросу из пропатченного VCRConnection ...
ОБНОВЛЕНИЕ № 2 2019-06-26
Казалось бы, невозможно получить доступ к VCRHTTPConnections, которые были исправлены при создании диспетчера контекста кассеты. Я открыл следующий запрос извлечения, чтобы передать в качестве аргументов при возникновении исключения, а затем перехватить и обработать произвольно вниз по течению.
https://github.com/kevin1024/vcrpy/pull/445
Относящиеся
Связанные вопросы, которые являются информативными, но все еще не отвечают на этот вопрос.