2

I Have two similar testcases with each having more than 100 lines of code which creates aux objects for the test, those lines are very similar, in fact, only 2 lines differ between the tests setups and I wan't to get rid of the code repetition. I think that multiple parameterization might help me with this task. Using multiple parametrization I can combine setups in the one and actually provide better conditions for the test. Bun I can't wrap my head around of what is the best way to specify the expected result while using multiple parametrization when each combination will yield different results?

Consider this test case

@pytest.mark.parametrize('country', ['US', 'DE', 'FR', 'IT'])
@pytest.mark.parametrize('number', ['12345', '54321'])
def test_correct_record_is_selected_for_number(country, number):
    # 100 line long setup of different objects.
    record = get_record(country, number)
    assert record = expected_record

I expect that for a different combinations of (country, number) get_record function will return different results, and the only way I see to provide the expected result - is to reimplement the part of the get_record function logic in the test itself in order to determine which result to expect based on provided country and number, which doesn't seem right to me.

Is there a way to nicely specify expected output for stacked parametrize decorators? Or I'm better off with moving setup code to multiple different fixtures and leaving it as 2 separate tests that use different setup fixtures?

Javed
  • 326
  • 3
  • 8
  • How did you create `expected_record` in case with two separate test? What is the`expected_record`, is comparable instance of class or some builtin type? – wiaterb Apr 18 '20 at 10:33
  • @wiaterb `expected_record` is a dictionary, in setup code I build static objects which are used internaly by `get_record` function. In two separate test cases I build those objects diffrently(2 lines of difference) Then, i get some of this static data i set up and put it in `expected_record` dict alongside with data that i expect to receive from `get_record` function – Javed Apr 18 '20 at 11:13

2 Answers2

5

Usually I put the results in a separate fixture that selects the expected value based on other arguments. Example:

expected_records = {'US': {'12345': 'fizz', '54321': 'buzz'}, 'DE': ...}


@pytest.fixture
def expected_record(request):
    country = request.node.funcargs['country']
    number = request.node.funcargs['number']
    return expected_records.get(country, dict()).get(number, None)


@pytest.mark.parametrize('country', ['US', 'DE', 'FR', 'IT'])
@pytest.mark.parametrize('number', ['12345', '54321'])
def test_correct_record_is_selected_for_number(country, number, expected_record):
    record = get_record(country, number)
    assert record == expected_record

However, in practice the data is rarely hardcoded in the script, so the logic of expected_record is usually data-driven, for example:

@pytest.fixture
def expected_record(request):
    country = request.node.funcargs['country']
    number = request.node.funcargs['number']
    file = pathlib.Path(request.config.rootdir, 'data', country).with_suffix('.json') # data/DE.json
    data = json.loads(file.read_text())  # {12345: "fizz", 54321: "buzz"}
    return data[number]
hoefling
  • 59,418
  • 12
  • 147
  • 194
  • Fantastic, couldn't even think of possibility to implement it this way. Does it considered best practice? Or maybe it widely used? Too bad I can't mark multiple answers as a solution :\ – Javed Apr 19 '20 at 07:52
  • 1
    I'm not sure whether it's really the best practice - essentially, this answer is not different from @MrBeanBremen's, the only difference is that the value selection is moved outside of the test. Therefore, on value selection error, the test will `error` (as the test preparation fails), while in his answer, the test will become `failed`. – hoefling Apr 19 '20 at 11:20
3

I'm not aware of a nice way, but in any case you have to list the different expected results somewhere. So you could just put them in a dictionary outside the test:

expected_record = {
    'US': {
        '12345': {...},
        '54321': {...},
    },
    'DE': {
        '12345': {...},
        '54321': {...},
    },
    ...
}

@pytest.mark.parametrize('country', ['US', 'DE', 'FR', 'IT'])
@pytest.mark.parametrize('number', ['12345', '54321'])
def test_correct_record_is_selected_for_number(country, number):
    record = get_record(country, number)
    assert record == expected_record[country][number]
Paul P
  • 3,346
  • 2
  • 12
  • 26
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
  • Awesome, I like the simplicity. Personally, I consider your solution clean and nice :) – Javed Apr 19 '20 at 07:47
  • Thanks. The solution proposed by @hoefling actually expands on this with the fixture, so his solution is certainly the nicer one :) – MrBean Bremen Apr 19 '20 at 09:57