Search code examples
pythonpytestgenerator

Pytest: How to map expected values to values of a pytest.fixture(params=...) with params


I'm starting on testing a project and chose pytest. While diving into it and trying to cut down on code duplication and verbosity while testing a generator I found that I can pass params to a fixture to test different instances of the generator. Until now I'm puzzled though how I can map expected values against the different instances and I have the feeling I attempt it with the wrong thought.

With an example Generator like:

class SomeGenerator:

    def __init__(self, stop: int):
        self.stop = stop

    def __iter__(self):
        for i in range(self.stop):
            yield i

Im trying to do something like this:

class TestGenerator:

    @pytest.fixture(params=[3, 4])
    def dummy_generator(self, request):
        return SomeGenerator(request.param)

    @pytest.fixture
    def dummy_generator_listed(self, dummy_generator):
        return list(dummy_generator)

    @pytest.fixture
    def dummy_generator_expected_list_1(self):
        return [0, 1, 2]

    @pytest.fixture
    def dummy_generator_expected_list_2(self):
        return [0, 1, 2, 3]

    # @pytest.mark.parametrize(??)
    def test_dummy_generator_output(self, dummy_generator_listed, expected_list):
        pass
        # something like:  assert expected_list == dummy_generator_listed

What I already tried

The closest attempt is this:

class TestGeneratorNotWorking:

    @pytest.fixture(params=[3, 4])
    def dummy_generator(self, request):
        return SomeGenerator(request.param)

    @pytest.fixture()
    def dummy_generator_listed(self, dummy_generator):
        return list(dummy_generator)

    @pytest.fixture()
    def dummy_generator_expected_list1(self):
        return [0, 1, 2]

    @pytest.fixture()
    def dummy_generator_expected_list2(self):
        return [0, 1, 2, 3]

    @pytest.mark.parametrize("expected_list", ["dummy_generator_expected_list1", "dummy_generator_expected_list2"])
    def test_dummy_generator_output(self, dummy_generator_listed, expected_list, request):
        assert request.getfixturevalue(expected_list) == dummy_generator_listed

This results in 4 Tests though and I only want dummy_generator_expected_list1 tested against the first instance of dummy_generator resulting in 2 Tests.

Small, but not needed addition

Additionally at best I would like to achieve something like this too:

   @pytest.fixture(param=[dummy_generator_expected_list_1, dummy_generator_expected_list_2])
    def dummy_expected_lists_grouped(self, expected_list):
        return expected_list

    def test_dummy_generator_output(self, dummy_generator_listed, dummy_expected_list_grouped):
        pass
        # something like:  assert dummy_expected_list_grouped == dummy_generator_listed

A scheme like this is would be wonderful in the actual implementation as the data of the generator is quite big and verbose so it would be absolutely unreadable to attempt it like:

    @pytest.mark.parametrize("expected_list", [[0, 1, 2], [0, 1, 2, 3]])
    def test_dummy_generator_output(self, dummy_generator_listed, expected_list):
        assert expected_list == dummy_generator_listed

And writing single tests for every expected generator and output has way too much duplication for my taste.

Thanks a lot in advance. As this is my first question posted here I hope I have given all resources needed in an understandable way


Solution

  • I think there is no need to use the fixture for that, as you only try to use one fixture param at a time (list1 only works while param=3). I would just parametrize both values like that:

    @pytest.mark.parametrize("param, expected_list", [
    (3, [0,1,2]), 
    (4, [0,1,2,3])
    ])
    def test_dummy_generator_output(self, param, expected_list):
        generator = SomeGenerator(param)
        assert list(generator) == expected_list
    

    Regarding your bonus addition - you can save the list of tuples in another place and just call it using the variable representing it.