参数化从 Pytest 或 Pytest-cases 中的 Fixture 返回的列表

Parameterize List Returned From Fixture in Pytest Or Pytest-cases

我正在尝试根据随机生成的数据编写测试装置。这个随机生成的数据需要能够接受一个种子,这样我们就可以同时在两台不同的计算机上生成相同的数据。

我正在使用 pytest parse.addoption 夹具(我认为它是一个夹具)来添加此功能。

我的核心问题是我希望能够参数化一个随机生成的列表,该列表使用夹具作为参数。

from secrets import randbelow

from pytest_cases import parametrize_with_cases, fixture, parametrize

def pytest_addoption(parser):
    parser.addoption("--seed", action="store", default=randbelow(10))

@fixture(scope=session)
def seed(pytestconfig):
    return pytestconfig.getoption("seed")

@fixture(scope=session)
def test_context(seed):
    # In my actual tests these are randomly generated from the seed.
    # each element here is actually a dictionary but I'm showing strings
    # for simplicity of example.
    return ['a', 'test', 'list']


@parametrize(group_item=test_context["group_items"])
def case_group_item(group_item: str): 
    return group_item, "expected_result_goes_here"


@parametrize_with_cases("sql_statement, expected_result", cases='.')
def test_example(
        sql_statement: str,
        expected_result: int) -> None:
    assert False

导致这个结果。

% pytest test.py
========================================================================================================================================================================== test session starts ===========================================================================================================================================================================
platform darwin -- Python 3.8.2, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/{Home}/tests, configfile: pytest.ini
plugins: datadir-1.3.1, celery-4.4.7, anyio-3.4.0, cases-3.6.11
collected 0 items / 1 error

================================================================================================================================================================================= ERRORS =================================================================================================================================================================================
________________________________________________________________________________________________________________________________________________________________________ ERROR collecting test.py ________________________________________________________________________________________________________________________________________________________________________
test.py:12: in <module>
    ???
E   TypeError: 'function' object is not subscriptable
======================================================================================================================================================================== short test summary info =========================================================================================================================================================================
ERROR test.py - TypeError: 'function' object is not subscriptable
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================================================================================================================================================ 1 error in 0.18s ============================================================================================================================================================================

我认为我可以通过进行空测试将 test_context 泄漏到全局范围来解决这个问题,但这感觉真的很脆弱。我正在寻找另一种方法仍然能够

  1. 使用seed fixture生成数据
  2. 为生成的列表中的每个元素生成一个测试
  3. 不依赖于测试的顺序运行。

编辑

这是一个不能直接使用 pytest 的例子

import pytest

from pytest_cases import parametrize_with_cases, fixture, parametrize


@fixture
def seed():
    return 1

@fixture
def test_context(seed):
    return [seed, 'a', 'test', 'list']

@pytest.fixture(params=test_context)
def example_fixture(request):
    return request.param

def test_reconciliation(example_fixture) -> None:
    print(example_fixture)
    assert False
pytest test.py
========================================================================================================================================================================== test session starts ===========================================================================================================================================================================
platform darwin -- Python 3.8.2, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/{HOME}/tests/integration, configfile: pytest.ini
plugins: datadir-1.3.1, celery-4.4.7, anyio-3.4.0, cases-3.6.11
collected 0 items / 1 error

================================================================================================================================================================================= ERRORS =================================================================================================================================================================================
________________________________________________________________________________________________________________________________________________________________________ ERROR collecting test.py ________________________________________________________________________________________________________________________________________________________________________
test.py:14: in <module>
    ???
../../../../../.venvs/data_platform/lib/python3.8/site-packages/_pytest/fixtures.py:1327: in fixture
    fixture_marker = FixtureFunctionMarker(
<attrs generated init _pytest.fixtures.FixtureFunctionMarker>:5: in __init__
    _inst_dict['params'] = __attr_converter_params(params)
../../../../../.venvs/data_platform/lib/python3.8/site-packages/_pytest/fixtures.py:1159: in _params_converter
    return tuple(params) if params is not None else None
E   TypeError: 'function' object is not iterable
======================================================================================================================================================================== short test summary info =========================================================================================================================================================================
ERROR test.py - TypeError: 'function' object is not iterable
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================================================================================================================================================ 1 error in 0.23s ======================================================================================================================================================================

在进一步挖掘之后,我 运行 进入了 this documentation around pytest-cases

from secrets import randbelow

import pytest
from pytest_cases import parametrize_with_cases, fixture, parametrize

def pytest_addoption(parser):
    parser.addoption("--seed", action="store", default=randbelow(1))

@fixture(scope="session")
def seed(pytestconfig):
    # return pytestconfig.getoption("seed")
    return 1

@pytest.fixture(scope="session")
def test_context(seed):
    # In my actual tests these are randomly generated from the seed.
    # each element here is actually a dictionary but I'm showing strings
    # for simplicity of example.
    return ['a', 'test', 'list']


@parametrize("group_item", [test_context])
def case_group_item(group_item: str): 
    return group_item, "expected_result_goes_here"


@parametrize_with_cases("sql_statement, expected_result", cases='.')
def test_example(
        sql_statement: str,
        expected_result: int) -> None:
    assert False


这很不幸 运行 我陷入了一个新问题。看起来 pytest-cases 当前没有在 fixture 执行步骤中调用 pytest_addoptionI created this ticket 来涵盖这种情况,但这确实有效地解决了我原来的问题,即使它有一个警告。

我用 testfile 和 conftest.py

试过你的代码

conftest.py

import pytest
from secrets import randbelow
from pytest_cases import parametrize_with_cases, fixture, parametrize


def pytest_addoption(parser):
    # If you add a breakpoint() here it'll never be hit.
    parser.addoption("--seed", action="store", default=randbelow(1))

@fixture(scope="session")
def seed(pytestconfig):
    # This line throws an exception since seed was never added.
    return pytestconfig.getoption("seed")

myso_test.py

import pytest
from pytest_cases import parametrize_with_cases, fixture, parametrize

@fixture(scope="session")
def test_context(seed):
    # In my actual tests these are randomly generated from the seed.
    # each element here is actually a dictionary but I'm showing strings
    # for simplicity of example.
    return ['a', 'test', 'list']

@parametrize("group_item", [test_context])
def case_group_item(group_item: str): 
    return group_item, "expected_result_goes_here"


@parametrize_with_cases("sql_statement, expected_result", cases='.')
def test_example(
        sql_statement: str,
        expected_result: int) -> None:
    assert True

测试运行:

PS C:\Users\AB45365\PycharmProjects\SO> pytest .\myso_test.py -s -v --seed=10 
============================================================== test session starts ==============================================================
platform win32 -- Python 3.9.2, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- c:\users\ab45365\appdata\local\programs\python\python39\python.exe       
cachedir: .pytest_cache
rootdir: C:\Users\AB45365\PycharmProjects\SO
plugins: cases-3.6.11, lazy-fixture-0.6.3
collected 1 item

myso_test.py::test_example[group_item-test_context] PASSED

补充 :从 pytest 7.1 开始,pytest_addoption 是一个 pytest 插件挂钩。因此,对于所有其他插件挂钩,它只能存在于插件文件或 conftest.py.

参见https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest.hookspec.pytest_addoption中的注释:

This function should be implemented only in plugins or conftest.py files situated at the tests root directory due to how pytest discovers plugins during startup.

因此,此问题与 pytest-cases 无关。