我正在使用pytest
,并在 conftest.py 中具有类似以下内容的内容:
def pytest_addoption(parser):
parser.addoption('--foo', required=True, help='Foo name.')
@pytest.fixture(scope='session')
def foo(pytestconfig):
with Foo(pytestconfig.getoption('foo')) as foo_obj:
yield foo_obj
我想将--foo
选项更改为
parser.addoption('--foo', action='append', help='Foo names.')
并具有一个单独的Foo
对象,其中为每个提供的名称生成会话范围。 通常,我会使用pytest_generate_tests
以这种方式对灯具进行参数化。 那是
def pytest_generate_tests(metafunc):
if 'foo' in metafunc.fixturenames:
metafunc.parametrize('foo', map(Foo, metafunc.config.getoption('foo')))
但是,如果我正确理解pytest_generate_tests
的工作原理,将为每个测试函数单独创建Foo
对象,从而破坏会话装置的全部意义。
似乎解决此问题的最简单方法是使夹具间接化。也就是说,您将配置值传递给灯具,但随后让它管理自己的设置。这样 pytest 将遵循灯具的范围设置。例如:
def pytest_generate_tests(metafunc):
# assuming --foo has been set as a possible parameter for pytest
if "foo" in metafunc.fixturenames and metafunc.config.option.foo is not None:
metafunc.parametrize("foo", metafunc.config.option.foo, indirect=True)
@pytest.fixture(scope='session')
def foo(request):
if not hasattr(request, 'param'):
pytest.skip('no --foo option set')
elif isinstance(request.param, str):
return Foo(request.param)
else:
raise ValueError("invalid internal test config")
总的来说,这看起来像:
conftest.py
def pytest_addoption(parser):
parser.addoption('--foo', action='append', help='Foo value')
test_something.py
import pytest
# keep track of how many Foos have been instantiated
FOO_COUNTER = 0
class Foo:
def __init__(self, value):
global FOO_COUNTER
self.value = value
self.count = FOO_COUNTER
FOO_COUNTER += 1
print(f'creating {self} number {self.count}')
def __repr__(self):
return f'Foo({self.value})'
def pytest_generate_tests(metafunc):
if "foo" in metafunc.fixturenames and metafunc.config.option.foo is not None:
metafunc.parametrize("foo", metafunc.config.option.foo, indirect=True)
@pytest.fixture(scope='session')
def foo(request):
if not hasattr(request, 'param'):
pytest.skip('no --foo option set')
elif isinstance(request.param, str):
return Foo(request.param)
else:
raise ValueError("invalid internal test config")
def test_bar(foo):
assert isinstance(foo, Foo)
assert foo.value in list('abx')
def test_baz(foo):
assert isinstance(foo, Foo)
assert foo.value in list('aby')
# test name is to encourage pytest to run this test last (because of side
# effects of Foo class has on FOO_COUNTER)
def test_zzz_last():
# only passes when exactly two foo options set
assert FOO_COUNTER == 2
如果正好使用两个--foo
选项运行,则test_zzz_last
通过。一个或三个--foo
选项,此测试失败。这表明每个--foo
选项只创建了一个Foo
实例,并且每个实例在测试之间共享。零--foo
选项将导致任何需要跳过foo
夹具的测试。
如果一个--foo
选项被赋予一个不是a
、b
、x
或y
的值,那么test_bar
和test_baz
都将失败。因此,我们可以看到我们的配置选项正在进入foo
夹具。