从 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 或每个模块的标记。