导致测试失败来自pytest autouse fixture

时间:2018-02-06 15:18:33

标签: unit-testing pytest fixtures

pytest允许创建自动应用于测试套件中每个测试的灯具(通过autouse关键字参数)。这对于实现影响每个测试用例的设置和拆除操作非常有用。有关详细信息,请参阅the pytest documentation

理论上,相同的基础设施对于验证每次测试运行后预期存在的后置条件也非常有用。例如,每次测试运行时都可能会创建一个日志文件,我想确保它在测试结束时存在。

不要挂断细节,但我希望你能得到基本的想法。关键是将此代码添加到每个测试函数会很繁琐和重复,尤其是当autouse灯具已经提供了将此操作应用于每个测试的基础架构时。此外,灯具可以打包到插件中,因此我的支票可以被其他包使用。

问题是它似乎不可能导致灯具测试失败。请考虑以下示例:

@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向我建议,这可能是不可能的。如果是这样的话,那么第二个问题是:还有另外一种方法吗?如果这个问题的答案也是"不是":为什么不呢?它是pytest基础设施的基本限制吗?如果没有,那么有没有计划支持这种功能?

3 个答案:

答案 0 :(得分:2)

在pytest中,yield-ing夹具在设置期间执行其定义的前半部分,在拆卸期间执行后半部分。此外,设置和拆卸不被视为任何单独测试的一部分,因此不会导致其失败。这就是您将异常报告为附加错误而不是测试失败的原因。

在一个哲学的注释中,就像你尝试过的方法一样(巧妙)方便,我认为它违反了测试设置和拆解的精神,因此即使你可以做到这一点,你不应该。存在设置和拆除阶段以支持测试的执行 - 不补充其系统行为的断言。如果行为足以断言,则断言非常重要,可以驻留在一个或多个专用测试的主体中。

如果您只是想尽量减少代码的重复,我建议将断言封装在辅助方法中,例如assert_log_file_cleaned_up(),可以从相应测试的主体中调用。这将允许测试机构保留其描述能力作为系统行为的规范。

答案 1 :(得分:0)

AFAIK不可能告诉pytest将特定夹具中的错误视为测试失败。

我也有一种情况,我想使用Fixture来最大程度地减少测试代码重复,但是在您的情况下,pytest-dependency可能是一种解决方法。

此外,测试依赖性对于非单元测试也不错,因此请谨慎使用autouse,因为它会使测试难以阅读和调试。测试函数标题中的显式固定装置至少为您提供了一些查找执行代码的指导。

答案 2 :(得分:0)

我更喜欢为此使用上下文管理器:

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

您当然不必使用标记,但是使用这些标记可以控制插件的范围。您也可以按班级或按模块设置标记。