我已经开始使用pytest-vcr
,它是pytest
的一个VCR.py
插件,包装在this blog post on Advanced Python Testing中。
它在第一次测试运行时将所有HTTP流量记录到cassettes/*.yml
文件中以保存快照。类似于针对Web组件的Jest快照测试。
在随后的测试运行中,如果请求格式错误,它将找不到匹配项,并抛出异常,表明禁止记录新请求,并且找不到现有记录。
> VCR.py
引发了一个CannotOverwriteExistingCassetteException
,它为什么不匹配的信息不多。
我该如何利用pytest
pytest_exception_interact
钩子将这一异常替换为具有更多信息的,利用固定装置信息?
我深入到site-packages
是VCR.py
的{{1}}中,并重写了我希望它如何处理异常。我只需要知道如何使此pip installed
钩子正常工作即可从该测试节点访问固定装置(在清理之前)并引发另一个异常。
让我们获取依赖关系。
pytest_exception_interact
test_example.py:
$ pip install pytest pytest-vcr requests
import pytest
import requests
@pytest.mark.vcr
def test_example():
r = requests.get("https://www.stackoverflow.com")
assert r.status_code == 200
现在将测试中的URI更改为“ https://www.google.com”:
test_example.py:
$ 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
...
然后再次运行测试以检测回归:
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/>)
...
文件,以创建一个本地插件,并且可以验证是否可以拦截该异常并使用以下命令注入自己的内容:< / p>
conftest.py
conftest.py
这是互联网上的文档变得很薄的部分。
如何访问
vcr_cassette
固定装置并返回其他异常?
我想做的是获取试图被请求的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)
,并且filtered_request
的列表并使用Python difflib标准库产生针对差异信息的增量。 / p>
使用pytest触发器cassette_requests
运行单个测试的内部机制,该触发器有效地运行以下三个pytest_runtest_protocol
调用以获取报告集合。
call_and_report
因此,我在 call 阶段修改了报告之后……但是仍然不知道如何访问灯具信息。
src/_pytest/runner.py:L166-L174
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
似乎有一些用于生成新ExceptionRepresentation的辅助方法,因此我更新了 conftest.py 示例。
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
更新#1 2019-06-26 :多亏了some pointers from @hoefling,我更新了 conftest.py 。
raise ... from ...
表单重新引发异常。longrepr = item.repr_failure(excinfo)
,将_vcr_marker
和vcr
固定装置附加到代表单个测试项目的vcr_cassette
上。更新#2 2019-06-26
获得在创建盒带上下文管理器时打补丁的VCRHTTPConnections似乎是不可能的。我打开了以下拉取请求,在引发异常时将其作为参数传递,然后任意捕获和处理下游数据。
https://github.com/kevin1024/vcrpy/pull/445
有意义的相关问题,但仍然无法回答该问题。
答案 0 :(得分:0)
感谢@hoefling的评论中的评论和指导。
我可以将cassette
固定装置附加到request.node
本地插件中的conftest.py
上,以覆盖pytest-vcr
标记...
@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_exception_interact
hook arthurHamon2
对于修复测试套件和集成匹配器差分输出非常有用。raise ... from ...
形式引发异常在撰写本文时(2019-07-01)合并的pull请求尚未发布到PyPI,因此使用从github链接安装pip的此选项将获得预发行版本:
pip install from git repo branch
pip install https://github.com/kevin1024/vcrpy/archives/master.zip
pytest_exception_interact
钩子在测试目录的根目录中,创建一个conftest.py
到create a local plugin,以覆盖pytest_exception_interact
hook。
@pytest.hookimpl(hookwrapper=True)
def pytest_exception_interact(node, call, report):
"""Intercept specific exceptions from tests."""
if report.when == "call" and isinstance(call.excinfo.value, CannotOverwriteExistingCassetteException):
__handle_cassette_exception(node, call, report)
yield
从异常中提取Cassette
和Request
。
# Define new exception to throw
class RequestNotFoundCassetteException(Exception):
...
def __handle_cassette_exception(node, call, report):
# Safely check for attributes attached to exception
vcr_request = None
cassette = None
if hasattr(call.excinfo.value, "cassette"):
cassette = call.excinfo.value.cassette
if hasattr(call.excinfo.value, "failed_request"):
vcr_request = call.excinfo.value.failed_request
# If we have the extra context available...
if cassette and vcr_request:
match_properties = [f.__name__ for f in cassette._match_on]
this_req, req_str = __format_near_match(cassette.requests, vcr_request, match_properties)
try:
raise RequestNotFoundCassetteException(f"{this_req}\n\n{req_str}\n") from call.excinfo.value
except RequestNotFoundCassetteException as e:
call.excinfo._excinfo = (type(e), e)
report.longrepr = node.repr_failure(call.excinfo)