如何在pytest中将几个参数化的夹具连接成一个新的夹具?
Posted
技术标签:
【中文标题】如何在pytest中将几个参数化的夹具连接成一个新的夹具?【英文标题】:How to concatenate several parametrized fixtures into a new fixture in py.test? 【发布时间】:2014-08-12 00:04:41 【问题描述】:如果我有两个参数化的夹具,我如何创建一个单独的测试函数,首先用一个夹具的实例调用,然后用另一个夹具的实例调用?
我想创建一个以某种方式连接两个现有夹具的新夹具是有意义的。这适用于“普通”灯具,但我似乎无法让它与参数化灯具一起使用。
这是我尝试过的一个简化示例:
import pytest
@pytest.fixture(params=[1, 2, 3])
def lower(request):
return "i" * request.param
@pytest.fixture(params=[1, 2])
def upper(request):
return "I" * request.param
@pytest.fixture(params=['lower', 'upper'])
def all(request):
return request.getfuncargvalue(request.param)
def test_all(all):
assert 0, all
当我运行它时,我得到了这个错误:
request = <SubRequest 'lower' for <Function 'test_all[lower]'>>
@pytest.fixture(params=[1, 2, 3])
def lower(request):
> return "i" * request.param
E AttributeError: 'SubRequest' object has no attribute 'param'
...upper()
出现同样的错误。
我做错了什么?
我该如何解决这个问题?
更新:
有一个 PyTest 插件可以用来解决这个问题:https://github.com/TvoroG/pytest-lazy-fixture。
pip
-安装此插件后,对上述代码唯一需要的改动如下:
@pytest.fixture(params=[pytest.lazy_fixture('lower'),
pytest.lazy_fixture('upper')])
def all(request):
return request.param
但请注意,在某些复杂的情况下它不起作用:
https://github.com/pytest-dev/pytest/issues/3244#issuecomment-369836702
相关的 PyTest 问题:
https://github.com/pytest-dev/pytest/issues/349 https://github.com/pytest-dev/pytest/issues/460 https://github.com/pytest-dev/pytest/issues/3244【问题讨论】:
我看到 py.test 跟踪器上有一个 issue 可能会解决我的问题,但 py.test 开发人员还没有回复。 有another issue 似乎与我的问题有关,但也没有回应......pytest-lazy-fixture
插件可以让你做到这一点。
@ChristianLong 感谢您的提示!我在上面添加了一些信息。
【参考方案1】:
现在pytest-cases
中有一个可用的解决方案,名为fixture_union
。以下是创建示例中请求的夹具联合的方法:
from pytest_cases import fixture_union, pytest_fixture_plus
@pytest_fixture_plus(params=[1, 2, 3])
def lower(request):
return "i" * request.param
@pytest_fixture_plus(params=[1, 2])
def upper(request):
return "I" * request.param
fixture_union('all', ['lower', 'upper'])
def test_all(all):
print(all)
它按预期工作:
<...>::test_all[lower-1]
<...>::test_all[lower-2]
<...>::test_all[lower-3]
<...>::test_all[upper-1]
<...>::test_all[upper-2]
请注意,我在上面的示例中使用了pytest_fixture_plus
,因为如果您使用pytest.fixture
,您将不得不自己处理未实际使用夹具的情况。这是按如下方式完成的,例如对于upper
夹具:
import pytest
from pytest_cases import NOT_USED
@pytest.fixture(params=[1, 2])
def upper(request):
# this fixture does not use pytest_fixture_plus
# so we have to explicitly discard the 'NOT_USED' cases
if request.param is not NOT_USED:
return "I" * request.param
详情请见documentation。 (顺便说一句,我是作者;))
【讨论】:
【参考方案2】:我有完全相同的question(并收到了类似但不同的answer)。我能想出的最好的解决方案是重新考虑我如何参数化我的测试。我没有使用多个具有兼容输出的固定装置,而是将固定装置用作常规函数,并将您的元固定装置参数化以接受函数名称和参数:
import pytest
def lower(n):
return 'i' * n
def upper(n):
return 'I' * n
@pytest.fixture(params=[
(lower, 1),
(lower, 2),
(upper, 1),
(upper, 2),
(upper, 3),
])
def all(request):
func, *n = request.param
return func(*n)
def test_all(all):
...
在您的特定情况下,将n
解压缩到列表中并使用*
传递它有点过分,但它提供了通用性。我的案例有所有接受不同参数列表的固定装置。
在 pytest 允许我们正确链接固定装置之前,这是我想出的唯一方法来运行 5 个测试而不是在您的情况下运行 12 个。您可以使用类似的方法缩短列表
@pytest.fixture(params=[
*[(lower, i) for i in range(1, 3)],
*[(upper, i) for i in range(1, 4)],
])
这样做有一个实际的优势。如果您的管道中有其他依赖项,您可以选择要执行特殊操作的测试,例如 XFAIL,而不会影响整个其他测试。
【讨论】:
嗯,灯具的要点是它们可以重复使用,对吧?如果我创建了一个参数化的夹具,我还想重新使用参数化(因为它是夹具的一部分)。你建议的方式,我不能重复使用参数化。想象一下有更多的装置,如lower
和upper
,我想对它们进行几种不同的组合。 lazy_fixture 插件让我可以完全做到这一点,而无需手动重复任何内容。【参考方案3】:
它并不美丽,但也许今天你知道更好的方法。
'all' 夹具内的请求对象只知道自己的参数:'lower','upper'。一种方式using fixtures from a fixture function。
import pytest
@pytest.fixture(params=[1, 2, 3])
def lower(request):
return "i" * request.param
@pytest.fixture(params=[1, 2])
def upper(request):
return "I" * request.param
@pytest.fixture(params=['lower', 'upper'])
def all(request, lower, upper):
if request.param == 'lower':
return lower
else:
return upper
def test_all(all):
assert 0, all
【讨论】:
感谢您的回答!但是同时使用lower
和upper
作为all()
的函数参数运行lower()
和upper()
的所有组合(我猜更准确地说是笛卡尔积)的测试,即6 次。将两个 params
添加到 all()
会使数字翻倍,即 test_all()
运行 12 次。我只想连接lower()
和upper()
,即只运行5次测试。以上是关于如何在pytest中将几个参数化的夹具连接成一个新的夹具?的主要内容,如果未能解决你的问题,请参考以下文章