从 pytest autouse fixture 导致测试失败
Cause test failure from pytest autouse fixture
pytest
允许创建自动应用于测试套件中每个测试的固定装置(通过 autouse
关键字参数)。这对于实施影响每个测试用例的设置和拆卸操作很有用。可以在 the pytest documentation.
中找到更多详细信息
理论上,相同的基础设施对于验证 post-每次测试运行后预期存在的条件也非常有用。例如,可能每次测试运行时都会创建一个日志文件,我想确保它在测试结束时存在。
不要纠结于细节,但我希望你能理解基本的想法。关键是,将此代码添加到每个测试功能将是乏味和重复的,尤其是当 autouse
固定装置已经提供将此操作应用于每个测试的基础结构时。此外,fixtures 可以打包成插件,所以我的检查可以被其他包使用。
问题是似乎不可能从固定装置导致测试失败。考虑以下示例:
@pytest.fixture(autouse=True)
def check_log_file():
# Yielding here runs the test itself
yield
# Now check whether the log file exists (as expected)
if not log_file_exists():
pytest.fail("Log file could not be found")
在日志文件不存在的情况下,我没有得到测试失败。相反,我收到一个 pytest 错误。如果我的测试套件中有 10 个测试,并且全部通过,但其中 5 个缺少日志文件,我将得到 10 个通过和 5 个错误。我的目标是获得 5 次通过和 5 次失败。
所以第一个问题是:这可能吗?我只是错过了什么吗? This answer 向我暗示这可能是不可能的。如果是这样的话,第二个问题是:还有别的办法吗?如果该问题的答案也是 "no":为什么不呢?这是 pytest
基础设施的基本限制吗?如果没有,那么有没有计划支持这种功能?
在 pytest 中,yield
-ing fixture 的前半部分定义在设置期间执行,后半部分在拆卸期间执行。此外,设置和拆卸不被视为任何单独测试的一部分,因此不会导致其失败。这就是为什么您将异常报告为 附加错误 而不是测试失败的原因。
从哲学的角度来看,尽管您尝试的方法可能(巧妙地)方便,但我认为它违反了测试设置和拆卸的精神,因此即使您 可以 做,你不应该。设置和拆卸阶段的存在是为了支持测试的执行——而不是为了补充其系统行为断言。如果行为重要到足以断言,断言就重要到足以驻留在一个或多个专用测试的主体中。
如果您只是想尽量减少代码重复,我建议将断言封装在辅助方法中,例如 assert_log_file_cleaned_up()
,可以从适当的测试主体中调用。这将使测试机构保留其作为系统行为规范的描述能力。
据我所知,无法告诉 pytest
将特定夹具中的错误视为测试失败。
我也有一个案例,我想使用 fixture 来最小化测试代码重复,但在你的情况下 pytest-dependency 可能是一种方法。
此外,测试依赖项对于 non-unit 测试来说并不坏,并且要小心 autouse
因为它会使测试更难阅读和调试。测试函数中的显式装置 header 至少为您提供了一些查找已执行代码的方向。
我更喜欢为此目的使用上下文管理器:
from contextlib import contextmanager
@contextmanager
def directory_that_must_be_clean_after_use():
directory = set()
yield directory
assert not directory
def test_foo():
with directory_that_must_be_clean_after_use() as directory:
directory.add("file")
如果您绝对负担不起为每个测试添加这一行,那么将其编写为插件就足够了。
将此放入您的 conftest.py
:
import pytest
directory = set()
# register the marker so that pytest doesn't warn you about unknown markers
def pytest_configure(config):
config.addinivalue_line("markers",
"directory_must_be_clean_after_test: the name says it all")
# this is going to be run on every test
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item):
directory.clear()
yield
if item.get_closest_marker("directory_must_be_clean_after_test"):
assert not directory
并将相应的标记添加到您的测试中:
# test.py
import pytest
from conftest import directory
def test_foo():
directory.add("foo file")
@pytest.mark.directory_must_be_clean_after_test
def test_bar():
directory.add("bar file")
运行 这会给你:
fail.py::test_foo PASSED
fail.py::test_bar FAILED
...
> assert not directory
E AssertionError: assert not {'bar file'}
conftest.py:13: AssertionError
当然,您不必使用标记,但这些允许控制插件的范围。您也可以使用每个 class 或每个模块的标记。
pytest
允许创建自动应用于测试套件中每个测试的固定装置(通过 autouse
关键字参数)。这对于实施影响每个测试用例的设置和拆卸操作很有用。可以在 the pytest documentation.
理论上,相同的基础设施对于验证 post-每次测试运行后预期存在的条件也非常有用。例如,可能每次测试运行时都会创建一个日志文件,我想确保它在测试结束时存在。
不要纠结于细节,但我希望你能理解基本的想法。关键是,将此代码添加到每个测试功能将是乏味和重复的,尤其是当 autouse
固定装置已经提供将此操作应用于每个测试的基础结构时。此外,fixtures 可以打包成插件,所以我的检查可以被其他包使用。
问题是似乎不可能从固定装置导致测试失败。考虑以下示例:
@pytest.fixture(autouse=True)
def check_log_file():
# Yielding here runs the test itself
yield
# Now check whether the log file exists (as expected)
if not log_file_exists():
pytest.fail("Log file could not be found")
在日志文件不存在的情况下,我没有得到测试失败。相反,我收到一个 pytest 错误。如果我的测试套件中有 10 个测试,并且全部通过,但其中 5 个缺少日志文件,我将得到 10 个通过和 5 个错误。我的目标是获得 5 次通过和 5 次失败。
所以第一个问题是:这可能吗?我只是错过了什么吗? This answer 向我暗示这可能是不可能的。如果是这样的话,第二个问题是:还有别的办法吗?如果该问题的答案也是 "no":为什么不呢?这是 pytest
基础设施的基本限制吗?如果没有,那么有没有计划支持这种功能?
在 pytest 中,yield
-ing fixture 的前半部分定义在设置期间执行,后半部分在拆卸期间执行。此外,设置和拆卸不被视为任何单独测试的一部分,因此不会导致其失败。这就是为什么您将异常报告为 附加错误 而不是测试失败的原因。
从哲学的角度来看,尽管您尝试的方法可能(巧妙地)方便,但我认为它违反了测试设置和拆卸的精神,因此即使您 可以 做,你不应该。设置和拆卸阶段的存在是为了支持测试的执行——而不是为了补充其系统行为断言。如果行为重要到足以断言,断言就重要到足以驻留在一个或多个专用测试的主体中。
如果您只是想尽量减少代码重复,我建议将断言封装在辅助方法中,例如 assert_log_file_cleaned_up()
,可以从适当的测试主体中调用。这将使测试机构保留其作为系统行为规范的描述能力。
据我所知,无法告诉 pytest
将特定夹具中的错误视为测试失败。
我也有一个案例,我想使用 fixture 来最小化测试代码重复,但在你的情况下 pytest-dependency 可能是一种方法。
此外,测试依赖项对于 non-unit 测试来说并不坏,并且要小心 autouse
因为它会使测试更难阅读和调试。测试函数中的显式装置 header 至少为您提供了一些查找已执行代码的方向。
我更喜欢为此目的使用上下文管理器:
from contextlib import contextmanager
@contextmanager
def directory_that_must_be_clean_after_use():
directory = set()
yield directory
assert not directory
def test_foo():
with directory_that_must_be_clean_after_use() as directory:
directory.add("file")
如果您绝对负担不起为每个测试添加这一行,那么将其编写为插件就足够了。
将此放入您的 conftest.py
:
import pytest
directory = set()
# register the marker so that pytest doesn't warn you about unknown markers
def pytest_configure(config):
config.addinivalue_line("markers",
"directory_must_be_clean_after_test: the name says it all")
# this is going to be run on every test
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item):
directory.clear()
yield
if item.get_closest_marker("directory_must_be_clean_after_test"):
assert not directory
并将相应的标记添加到您的测试中:
# test.py
import pytest
from conftest import directory
def test_foo():
directory.add("foo file")
@pytest.mark.directory_must_be_clean_after_test
def test_bar():
directory.add("bar file")
运行 这会给你:
fail.py::test_foo PASSED
fail.py::test_bar FAILED
...
> assert not directory
E AssertionError: assert not {'bar file'}
conftest.py:13: AssertionError
当然,您不必使用标记,但这些允许控制插件的范围。您也可以使用每个 class 或每个模块的标记。