我有一个生成器功能
def foo():
resource = setup()
yield resource
tidy(resource)
我用它作为固定装置
@pytest.fixture
def foofix():
yield from foo()
这个效果很好。
我想测试
foo
def test_foo():
res_gen = foo()
res = next(res_gen)
assert res.is_working
但是因为我在
resource
之后立即清理了 yield
,所以不再可以断言它正在工作。那么 pytest 在整理之前如何使用 resource
,我该如何做同样的事情?
如果你想测试 foo,你只需在代码中调用它,而不是将其包装在 pytest 固定装置中。
夹具对于需要生成一些样板的值很有用,并且,当这些值必须在测试后整理时,以对测试透明的方式 - 以便测试可以消耗该值,并执行不用担心任何样板。
如果您想测试foo
生成器本身,而不仅仅是在测试中消耗其值,则将其包装在固定装置中是没有意义的 - 只需将其或其模块导入包含测试函数的模块中,通过调用启动生成器,然后调用
next
并捕获
StopIteration
(如果需要)。当你跑步时
def test_foo():
res_gen = foo()
res = next(res_gen)
assert res.is_working
作为普通的 Python 生成器,在 next
上调用
foo()
后,您将在释放资源之前获得资源:当执行
yield resource
时,生成器中的执行会在
assert res.is_working
表达式处暂停。只有在随后调用
next
时,
tidy(resource)
线路才会运行。实际上,这是您设计中的
另一个问题:生成器不会以任何方式确保它们将运行完成(因此完成您的资源) - 在这样的模式中,由调用者调用next
直到发电机耗尽。当您将生成器标记为 pytest 固定装置时,情况会有所不同 - 在这种情况下,
pytest 将在第一次产量之后、测试结束后再次运行生成器 - 但这是 pytest“好东西”,因为它使用这种模式来启动和清理固定装置,而不是发电机的自然行为。
与之接近的模式是使用 Python 的contextlib.contextmanager
调用来装饰这样的生成器,然后您的生成器将转换为上下文管理器,可以用作
with
命令中的表达式。当
with
命令结束时,执行
yield
下面的生成器代码。所以,也许你想要这个:
from contextlib import contextmanager
@contextmanager
def foo():
resource = setup()
# try/finally block is needed, otherwise, if an exception
# occurs where resource is used in a `with` block,
# the finalization code is not executed.
try:
yield resource
finally:
tidy(resource)
def test_foo():
with foo() as res:
# here, you have "resource" and it has not terminated
assert res.is_working
# the end of the "with" block resumes the generator and frees the resource
return # even if it is an implicit return.