代码之家  ›  专栏  ›  技术社区  ›  AlexLordThorsen

Pytest或Pytest案例中从夹具返回的参数化列表

  •  0
  • AlexLordThorsen  · 技术社区  · 3 年前

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

    我在用pytest parse.addoption fixture(我认为这是一个fixture)来增加这种能力。

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

    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. 使用种子夹具生成数据
    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 ======================================================================================================================================================================
    
    0 回复  |  直到 3 年前
        1
  •  2
  •   Devang Sanghani    3 年前

    我用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
    
        2
  •  1
  •   smarie    3 年前

    补充 Devang Sanghani's answer :截至pytest 7.1, pytest_addoption 是pytest插件挂钩。因此,对于所有其他插件挂钩,它只能出现在插件文件中或 conftest.py

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

    此函数只能在插件或conftest.py中实现 由于pytest的发现方式,位于测试根目录中的文件 插件。

    因此,该问题与 pytest-cases

        3
  •  0
  •   AlexLordThorsen    3 年前

    在做了更多的挖掘之后,我遇到了 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案例当前未调用 pytest_addoption 现在在夹具执行步骤rihgt期间。 I created this ticket 以涵盖这个案例,但这确实有效地解决了我最初的问题,即使它有一个警告。