如何在py.test中将几个参数化夹具连接成一个新的夹具?

问题描述 投票:6回答:3

如果我有两个参数化夹具,我怎么能创建一个单独的测试函数,首先用一个夹具的实例调用,然后用另一个夹具的实例调用?

我想创建一个以某种方式连接两个现有灯具的新灯具是有意义的。这适用于“普通”灯具,但我似乎无法使用参数化灯具。

这是我尝试过的简化示例:

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问题:

python fixtures pytest
3个回答
1
投票

它并不美丽,但今天你可能知道更好的方式。

“all”夹具内的请求对象只知道自己的params:'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

1
投票

我有完全相同的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,如果您的管道中有其他依赖项,则不会影响整个其他测试。


0
投票

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。 (顺便说一下,我是作者;))

© www.soinside.com 2019 - 2024. All rights reserved.