pytest:如何使用标记注入夹具?

pytest: how to use a mark to inject a fixture?

我正在编写一个带有固定装置的 pytest 插件,该固定装置具有设置一些理想模拟的副作用。我想写一个简单的标记,允许用户在测试运行之前调用这个夹具设置,而不必在测试函数参数中包含夹具——本质上,"injecting" 使用标记的夹具。我的理由是用户可能想要模拟设置而不需要夹具本身的 return 值,在这种情况下,使用标记对我来说似乎比要求他们声明他们不会声明的夹具更直观正在使用。

我如何使用标记来要求 pytest 中的夹具?查看文档,似乎我想连接到 pytest_collection_modifyitems 之类的东西,使用 Item.iter_markers 检查每个项目上的相关标记,然后以某种方式更新固定装置列表。然而,阅读代码时,我无法弄清楚如何准确地触发该夹具设置。

这里有一个简单的示例,说明了所讨论的灯具的外观:

@pytest.fixture
def mocks(mocker):
    ret_val = 10
    mocker.patch('path.to.patch', return_value=ret_val)
    return ret_val

用户现在可以执行以下操作来设置模拟:

def test_mocks(mocks):
    # 'path.to.patch' will be mocked in this test
    # ... test code ...

但是如果夹具可以通过标记触发,测试可能会是这样的:

@pytest.mark.mocks
def test_mocks():
    # 'path.to.patch' will be mocked in this test, too
    # ... test code ...

使用 usefixtures 标记:

# conftest.py
import pytest

@pytest.fixture
def mocks(mocker):
    mocker.patch('os.path.isdir', return_value=True)

# test_me.py
import os
import pytest

@pytest.mark.usefixtures('mocks')
def test_mocks():
    assert os.path.isdir('/this/is/definitely/not/a/dir')

也可以传递多个灯具:

@pytest.mark.usefixtures('mocks', 'my_other_fixture', 'etc')

但是,有一个警告:代码中的 mocks 夹具 returns 值 ret_val。当通过测试函数 args 传递 fixture 时,该值在 mocks arg 下返回;当您使用标记时,您不再拥有 arg,因此您将无法使用该值。如果您需要模拟值,请在测试函数 args 中传递夹具。还有其他一些可以想象的方法,比如通过缓存传递ret_val,但是,生成的代码会更复杂且可读性较差,所以我不会打扰。

我能够通过使用 pytest_collection_modifyitems hook 在收集后调整测试项目上的固定装置列表来实现这一点:

@pytest.hookimpl(trylast=True)
def pytest_collection_modifyitems(items):
    '''
    Check if any tests are marked to use the mock.
    '''
    for item in items:
        # Note that `get_marker` has been superceded by `get_closest_marker`
        # in the development version of pytest
        if item.get_marker('mocks'):
            item.fixturenames.append('mocks')

收集后调整 Item.fixturenames 列表似乎以我希望的方式触发夹具设置。

但是,如果您不关心使用自定义标记,@hoefling 建议使用内置 usefixtures 标记也是一个很好的解决方案。