【问题标题】:How to pass a fixture that returns a variable-length iterable of values to pytest.mark.parameterize?如何将返回可变长度可迭代值的夹具传递给 pytest.mark.parameterize?
【发布时间】:2022-08-24 05:32:50
【问题描述】:

我有一个产生可迭代的 pytest 夹具,我想使用此可迭代中的项目参数化测试,但我无法找出正确的语法。

有谁知道如何使用夹具的值对测试进行参数化?这是一些显示我当前方法的虚拟代码:

import pytest

@pytest.fixture()
def values():
    return [1, 1, 2]

@pytest.mark.parametrize(\'value\', values)
def test_equal(value):
    assert value == 1
  • 这回答了你的问题了吗? How can I pass fixtures to pytest.mark.parameterize?
  • 谢谢你的建议。不幸的是它没有。我想传递给pytest.mark.parameterize 的fixture 是一个可以是可变长度的大型迭代,因此在我的情况下创建一个单独的fixture 的迭代是行不通的。
  • 抱歉,如果该链接不清楚,但重点不是制作“单独的固定装置”,而是更多关于使用 parametrize\ 的 indirect= 参数,该参数“懒惰”评估来自夹具。确认一下,你不知道预先values 列表/可迭代的大小/长度?因为您可以使该夹具接受一个索引参数,而 indirect-ly 一次返回 1 个值。如果长度未知预先(可能是edit -ed 到问题中),那么这就很棘手了。无论哪种方式,我都撤回了我的近距离投票。
  • 不是直接重复,因为这个问题没有提到pytest_generate_hook,但由于我们在这里的回答建议使用那个钩子,这可能是相关的:Is it possible to use a fixture inside pytest_generate_tests()?(TL;DR:不,这也不可能。)

标签: python pytest


【解决方案1】:

简短的回答是pytest doesn't support passing fixtures to parametrize

pytest 提供的开箱即用解决方案是使用indirect parametrization 或使用pytest_generate_tests 定义您自己的参数化方案,如How can I pass fixtures to pytest.mark.parameterize? 中所述

这些是我以前用来解决此问题的解决方法。

选项 1:生成values 的单独函数

from typing import Iterator, List
import pytest

def generate_values() -> Iterator[str]:
    # ... some computationally-intensive operation ...
    all_possible_values = [1, 2, 3]
    for value in all_possible_values:
        yield value

@pytest.fixture()
def values() -> List[str]:
    return list(generate_values())

def test_all_values(values):
    assert len(values) > 5

@pytest.mark.parametrize("value", generate_values())
def test_one_value_at_a_time(value: int):
    assert value == 999
$ pytest -vv tests
...
========================================================== short test summary info ===========================================================
FAILED tests/test_main.py::test_all_values - assert 3 > 5
FAILED tests/test_main.py::test_one_value_at_a_time[1] - assert 1 == 999
FAILED tests/test_main.py::test_one_value_at_a_time[2] - assert 2 == 999
FAILED tests/test_main.py::test_one_value_at_a_time[3] - assert 3 == 999

主要变化是将值列表的生成移动到常规的非固定函数generate_values。如果它是一个静态列表,那么您甚至可以放弃将其设为函数,而只需将其定义为常规模块级变量。

ALL_POSSIBLE_VALUES = [1, 2, 3]

并非所有东西都需要固定。将测试数据注入函数是有利的,是的,但这并不意味着您不能使用常规的 Python 函数和变量。此解决方案的唯一问题是生成值列表是否依赖于其他装置,即reusable fixtures。在这种情况下,您也必须重新定义它们。

我在这里保留了values 固定装置,用于您需要进行的测试全部可能的值作为一个列表,如test_all_values

如果这个值列表将用于多个其他测试,而不是用parametrize 装饰每个测试,您可以在pytest_generate_tests 钩子中执行此操作。

def pytest_generate_tests(metafunc: pytest.Metafunc):
    if "value" in metafunc.fixturenames:
        metafunc.parametrize("value", generate_values())

def test_one_value_at_a_time(value: int):
    assert value == 999

这个选项避免了很多重复,然后您甚至可以将generate_values 更改为您需要的任何内容,或者您​​需要它独立于测试和测试框架。

选项 2:使用 indirect 并让夹具一次返回 1 个值

如果有可能知道值列表的长度预先(就像在运行测试之前一样),然后您可以使用parametrizeindirect=,然后让夹具一次只返回一个值。

# Set/Defined by some env/test configuration?
MAX_SUPPORTED_VALUES = 5

@pytest.fixture
def value(request: pytest.FixtureRequest) -> int:
    all_possible_values = [1, 2, 3, 4, 5]
    selected_index = request.param
    return all_possible_values[selected_index]

@pytest.mark.parametrize("value", range(MAX_SUPPORTED_VALUES), indirect=True)
def test_one_value_at_a_time(value: int):
    assert value == 999

主要的变化是让夹具接受一个参数(一个索引),然后返回该索引处的值。 (还将夹具从 values 重命名为 value 以匹配返回值)。然后,在测试中,您使用indirect=True,然后传递一系列索引,这些索引以request.param 的形式传递给fixture。

同样,这仅在您至少知道值列表的长度时才有效。

同样,您可以使用pytest_generate_tests,而不是为使用此夹具的每个测试应用parametrize 装饰器:

def pytest_generate_tests(metafunc: pytest.Metafunc):
    if "value" in metafunc.fixturenames:
        metafunc.parametrize("value", range(MAX_SUPPORTED_VALUES), indirect=True)

def test_one_value_at_a_time(value: int):
    assert value == 999

【讨论】:

    【解决方案2】:

    你必须添加一个conftest文件,移动你的fixture,修改它们,最后再添加一个函数:)

    第一个文件:

    # content of the test_<your_filename>.py
    
    def test_equal(values):
       assert values == 1
    

    第二个文件:

    # content of the conftest.py
    
    def pytest_generate_tests(metafunc):
       if "values" in metafunc.fixturenames:
           metafunc.parametrize("values", [1, 2, 3], indirect=True)
    
    
    @pytest.fixture
    def values(request):
       return request.param
    

    夹具中的request_pytest.fixtures.SubRequest 实例。而parampytest_generate_tests 列表中的参数。

    和测试输出:

    
    platform linux -- Python 3.8.10, pytest-7.1.2, pluggy-1.0.0
    rootdir: /mnt/d/temp
    collected 3 items
    
    test_aa.py .FF                                                     [100%]
    
    ================================ FAILURES ================================
    ______________________________ test_equal[2] _____________________________
    
        def test_equal(values):
    >       assert values == 1
    E       assert 2 == 1
    
    test_aa.py:3: AssertionError
    ______________________________ test_equal[3] _____________________________
    values = 3
    
        def test_equal(values):
    >       assert values == 1
    E       assert 3 == 1
    
    test_aa.py:3: AssertionError
    ======================== short test summary info ========================
    FAILED test_aa.py::test_equal[2] - assert 2 == 1
    FAILED test_aa.py::test_equal[3] - assert 3 == 1
    ====================== 2 failed, 1 passed in 0.97s ======================
    

    您可以在 pytest 文档的this part 中找到更多详细信息和示例。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-26
    • 2019-07-07
    • 1970-01-01
    • 2012-06-22
    • 2010-11-06
    • 2014-10-14
    • 1970-01-01
    相关资源
    最近更新 更多