Passing (yield) fixtures as test parameters (with a temp directory)
是否可以将生成的pytest固定装置(用于设置和拆卸)作为参数传递给测试函数?
上下文
我正在测试一个对象,该对象在单个目录中读写文件。该目录的路径另存为对象的属性。
我在以下方面遇到了麻烦:
1 2 3 4 5 6 7 8 9 10 11 | ================================== FAILURES =================================== ______________________________ test_attr[thing0] ______________________________ thing = <generator object thing1 at 0x0000017B50C61BF8> @pytest.mark.parametrize('thing', [thing1, thing2]) def test_attr(thing): > print(thing.datadir) E AttributeError: 'function' object has no attribute 'props' test_mod.py:39: AttributeError |
因此,fixture函数没有我的类的属性。足够公平。
尝试1
一个函数将没有属性,因此我尝试调用该函数以实际获取对象。但是,只是
1 2 3 4 | @pytest.mark.parametrize('thing', [thing1(), thing2()]) def test_attr(thing): print(thing.props['datadir']) assert os.path.exists(thing.get('datadir')) |
的结果是:
1 2 3 4 5 6 7 8 9 10 11 | ================================== FAILURES =================================== ______________________________ test_attr[thing0] ______________________________ thing = <generator object thing1 at 0x0000017B50C61BF8> @pytest.mark.parametrize('thing', [thing1(), thing2()]) def test_attr(thing): > print(thing.datadir) E AttributeError: 'generator' object has no attribute 'props' test_mod.py:39: AttributeError |
尝试2
我也尝试在
1 2 3 4 5 6 7 8 9 | ================================== FAILURES =================================== ______________________________ test_attr[thing0] ______________________________ thing = <test_mod.Thing object at 0x000001C528F05358> @pytest.mark.parametrize('thing', [thing1(), thing2()]) def test_attr(thing): print(thing.datadir) > assert os.path.exists(thing.datadir) |
Closing
重述问题:无论如何,是否有将这些固定装置作为参数传递并维护临时目录的清理?
尝试将您的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import pytest, tempfile, os, shutil from contextlib import contextmanager @pytest.fixture # This works with pytest>3.0, on pytest<3.0 use yield_fixture def datadir(): datadir = tempfile.mkdtemp() # setup yield datadir shutil.rmtree(datadir) # teardown class Thing: def __init__(self, datadir, errorfile): self.datadir = datadir self.errorfile = errorfile @pytest.fixture def thing1(datadir): errorfile = os.path.join(datadir, 'testlog1.log') yield Thing(datadir=datadir, errorfile=errorfile) @pytest.fixture def thing2(datadir): errorfile = os.path.join(datadir, 'testlog2.log') yield Thing(datadir=datadir, errorfile=errorfile) @pytest.mark.parametrize('thing_fixture_name', ['thing1', 'thing2']) def test_attr(request, thing): thing = request.getfixturevalue(thing) # This works with pytest>3.0, on pytest<3.0 use getfuncargvalue print(thing.datadir) assert os.path.exists(thing.datadir) |
再进一步,您可以对
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Thing: def __init__(self, datadir, errorfile): self.datadir = datadir self.errorfile = errorfile @pytest.fixture(params=['test1.log', 'test2.log']) def thing(request): with tempfile.TemporaryDirectory() as datadir: errorfile = os.path.join(datadir, request.param) yield Thing(datadir=datadir, errorfile=errorfile) def test_thing_datadir(thing): assert os.path.exists(thing.datadir) |
临时目录和文件由pytest使用内置的夹具tmpdir和tmpdir_factory处理。
对于这种用法,tmpdir应该足够了:https://docs.pytest.org/en/latest/tmpdir.html
此外,在此示例中,经过参数化的灯具也可以很好地工作。记录在这里:https://docs.pytest.org/en/latest/fixture.html#fixture-parametrize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import os import pytest class Thing: def __init__(self, datadir, errorfile): self.datadir = datadir self.errorfile = errorfile @pytest.fixture(params=(1, 2)) def thing(request, tmpdir): errorfile_name = 'testlog{}.log'.format(request.param) errorfile = tmpdir.join(errorfile_name) return Thing(datadir=str(tmpdir), errorfile=str(errorfile)) def test_attr(request, thing): assert os.path.exists(thing.datadir) |
BTW,在pytest的Python测试中,ch3涵盖了参数化的灯具。 ch4中涵盖了tmpdir和其他内置固定装置。
我看到了您的问题,但不确定解决方案。问题:
您的函数thing1和thing2包含yield语句。当您调用这样的函数时,返回的值是一个" generator object。"。它是一个迭代器-一系列值,这当然与
这些是传递给
您真正想要的是在
一种可能性是迭代生成器一次。像这样:
1 2 3 4 | def test_attr(thing): first_thing = next(thing) print(first_thing.datadir) assert os.path.exists(first_thing.datadir) |
但这只是第一个障碍。生成器功能未完成。其内部的"程序计数器"位于
或者我认为这会起作用:
1 2 3 4 | def test_attr(thing): for a_thing in thing: print(a_thing.datadir) assert os.path.exists(a_thing.datadir) |
对我来说,这是否使您的代码或多或少地易于阅读和可维护是一个悬而未决的问题。有点笨拙。