How to concatenate several parametrized fixtures into a new fixture in py.test?

后端 未结 3 1511
清酒与你
清酒与你 2020-12-30 04:46

If I have two parametrized fixtures, how can I create a single test function that is called first with the instances of one fixture and then with the instances of the other

相关标签:
3条回答
  • 2020-12-30 05:26

    I had the exact same question (and received a similar, but distinct answer). The best solution I was able to come up with was to reconsider how I parametrize my tests. Instead of having multiple fixtures with compatible outputs, I used the fixtures as regular functions, and just parametrized your meta-fixture to accept the function name and arguments:

    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):
        ...
    

    In your particular case, unpacking n into a list and passing it with * is slightly overkill, but it provides generality. My case has fixtures that all accept different parameter lists.

    Until pytest allows us to properly chain fixtures, this is the only way I have come up with to run 5 tests instead of 12 in your situation. You can make the list shorter with something like

    @pytest.fixture(params=[
        *[(lower, i) for i in range(1, 3)],
        *[(upper, i) for i in range(1, 4)],
    ])
    

    There is an actual advantage of doing it this way. You can pick and chose which tests you want to do special things to, like XFAIL, without affecting a whole swath of other tests if you have additional dependencies in your pipeline.

    0 讨论(0)
  • 2020-12-30 05:33

    There is now a solution available in pytest-cases, named fixture_union. Here is how you create the fixture union that you requested in your example:

    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)
    

    It works as expected:

    <...>::test_all[lower-1] 
    <...>::test_all[lower-2] 
    <...>::test_all[lower-3] 
    <...>::test_all[upper-1] 
    <...>::test_all[upper-2] 
    

    Note that I used pytest_fixture_plus in the above example because if you use pytest.fixture you will have to handle yourself the cases where a fixture is not actually used. This is done as follows, for example for the upper fixture:

    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
    

    See documentation for details. (I'm the author by the way ;) )

    0 讨论(0)
  • 2020-12-30 05:50

    It is not beautiful, but may be today you know the better way.

    Request object inside 'all' fixture know only about own params: 'lower', 'upper'. One way 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
    
    0 讨论(0)
提交回复
热议问题