python - 在运行时修改(或包装)pytest 函数
问题描述
我需要获取一个函数体并将其包装在一个 try-finally 中。try-finally 必须进入函数体。
一些背景知识:我有使用特定夹具的 pytest 函数,我们将其称为check
. 该对象用于进行断言,同时在 plain 之上添加额外的功能assert
。一种能力是将实际断言推迟到最后,这样测试就不必在失败时提前结束。因此,该check
对象有一个必须在其上调用的拆卸/终结器函数,check.finish()
. 在调用该函数之前,不会实际评估断言。
我想把.finish()
电话放在夹具中,像这样
@pytest.fixture
def check():
_check = ...
yield _check
_check.finish()
然而,这还不够好,因为 pytest 和其他测试基础设施将AssertionError
在夹具拆解中抛出的解释为Error
,与 test 不同Failure
,这是我想保留的区别。
所以check.finish()
调用必须在测试函数体中:
def test_something(check):
check.that(some_variable, "==", some_value)
check.finish()
我想减轻这个样板文件并确保check.finish()
始终调用它,即使测试主体抛出异常也是如此。到目前为止,我最好的想法(听起来很老套)是用来inspect.getsource()
抓取函数体并将其包装在 try-finally 中,以便在运行时像这样评估函数:
def test_something(check):
try:
check.that(some_variable, "==", some_value)
finally:
check.finish()
即使测试将像这样存储在存储库中
def test_something(check):
check.that(some_variable, "==", some_value)
我对这个解决方案的尝试显示在这里,它不起作用,因为它在缩进枚举步骤中访问了越界的行号,我认为enumerate()
这应该防止。
def print_source(source_lines):
"""for debugging, adds line numbers"""
print("".join((f"{str(i).zfill(3)}|{line}" for i, line in enumerate(source_lines))))
def add_try_finally_check_finish(fn: Callable):
indent = " " * 4
source_lines, _ = inspect.getsourcelines(fn)
print_source(source_lines)
# find beginning of function
# assume the line after a `):` is the first line
line_number = 0
for line_number, line in enumerate(source_lines):
if "):" in line:
break
# add `try:`
try_line_number = line_number + 1
source_lines.insert(try_line_number, f"{indent}try:\n")
print_source(source_lines)
# continue iterating and indent everything
for line_number, line in enumerate(source_lines, start=try_line_number):
source_lines[line_number] = indent + line
# add teardown
teardown = [
f"{indent}finally:\n",
f"{indent*2}check.finish()\n",
]
source_lines.extend(teardown)
print_source(source_lines)
return compile("".join(source_lines), inspect.getsourcefile(fn), mode="exec")
@pytest.fixture
def check(request):
_check = ...
request.function.__code__ = add_try_finally_check_finish(request.function)
yield _check
或者,这可以用装饰器来完成吗?这比在运行时更改源代码更好,但是我不知道如何编写这样的装饰器并将check
对象传递给它。我在这里找到了其他可以成功装饰 pytest 函数的答案,但没有一个也可以访问固定装置。
@always_finish
def test_something(check):
check.that(some_variable, "==", some_value)
这里的核心要求是,从 pytest 的角度来看,check.finish()
在测试本身而不是在拆卸期间调用,即使测试本身抛出异常,所以欢迎其他实现这一点的创造性解决方案。
解决方案
推荐阅读
- laravel - Laravel 8/Fortify/Jetstream/Multitenancy:配置子域租户标识时出错:“未选择数据库”
- javascript - How to get audio data from NodeJS MongoDB GridFS and play it in browser?
- powerbi - Power BI 小计计算
- azure - 如何修复“Microsoft.Azure.KeyVault.Models.KeyVaultErrorException:'操作返回无效状态代码'NotFound''”
- javascript - 如何在 Wordpress 中使用 html、css 和 javascript 添加带有计时器的下载按钮
- javascript - JS 作为常规字符串
- python - 如何根据最高匹配列映射来自另一个数据帧的值
- javascript - 仅向有权访问面向环境的摄像头的用户显示功能
- c# - 想要使用 C# 和 Windows 窗体将两个不同表中的 ID 添加到一个关联表中
- logging - 为什么要使用“%c”在日志中格式化日期?