Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pytest text annotation for test with tuple of parameters

Tags:

python

pytest

I'm looking for more elegant solution for this kind of problem:

def ids(x):
    if isinstance(x, int):
        return str(x)
    elif isinstance(x, str):
        return x[0]


@pytest.mark.parametrize("number, string",
                         [
                             (1, "One"),
                             (2, "Two"),
                             (3, "Three")
                         ],
                         ids=ids)
def test_stupid(number, string):
    assert 0 == 1

This code will produce test names: '1-O', '2-T', '3-T'.

The problem is that pytest is using same function for all arguments in tuple and I have to deal with ugly isinstance calls.

Is there a better way to solve this problem?

like image 241
Rabodaber Avatar asked May 31 '16 11:05

Rabodaber


People also ask

Can pytest fixtures have arguments?

You can pass arguments to fixtures with the params keyword argument in the fixture decorator, and you can also pass arguments to tests with the @pytest. mark. parametrize decorator for individual tests.

What is pytest Mark Parametrize do?

@pytest. mark. parametrize allows one to define multiple sets of arguments and fixtures at the test function or class. pytest_generate_tests allows one to define custom parametrization schemes or extensions.

What is Conftest py in pytest?

conftest.py is where you setup test configurations and store the testcases that are used by test functions. The configurations and the testcases are called fixture in pytest.


2 Answers

Pass a list of strings instead of a callable as ids:

import pytest

PARAMS = [
    (1, "One"),
    (2, "Two"),
    (3, "Three")
]


def my_id(number, string):
    return '-'.join([str(number), string[0]])


@pytest.mark.parametrize("number, string",
                         PARAMS,
                         ids=[my_id(number, string) for number, string in PARAMS])
def test_stupid(number, string):
    assert 0 == 1
like image 149
das-g Avatar answered Oct 11 '22 02:10

das-g


The only solution i've found is to use hook pytest_collection_modifyitems(session, config, items) It has access to parameters values, and can be used to change test names. But it relies on internal details of pytest Function class implementation and thus is not very robust.

I add pytest.mark with format string and converters for each parameter. All parameters optional

@pytest.mark.parametrize("number, string",
                     [
                         (1, "One"),
                         (2, "Two"),
                         (3, "Three")
                     ])
@pytest.mark.params_format('number={number} string={string}',
                           number=lambda x: str(x),
                           string=lambda x: x[0])
def test_stupid(number, string):

And then add to conftest.py hook implementation to use that mark parameters and add formatted string to test

def pytest_collection_modifyitems(session, config, items):
    for item in items:
        if not _get_marker(item, 'parametrize'):
            continue

        format_params_marker = _get_marker(item, 'params_format')
        if not format_params_marker:
            continue

        params = item.callspec.params.copy()
        param_formatters = format_params_marker.kwargs
        for param_name, presenter in param_formatters.iteritems():
            params[param_name] = presenter(params[param_name])

        params_names_ordered = [name for name in item.fixturenames if name in params]
        default_format = '-'.join(['{{{}}}'.format(param_name) for param_name in params_names_ordered])
        format_string = format_params_marker.args[0] if format_params_marker.args \
            else default_format
        item.name = '{func}[{params}]'.format(func=item.name.split('[')[0],
                                              params=format_string.format(**params))

def _get_marker(item, marker):
    return item.keywords._markers.get(marker, None)

The result will look something like this

test_stupid[number=1 string=O] PASSED
test_stupid[number=2 string=T] PASSED
test_stupid[number=3 string=T] PASSED
like image 21
igogorek Avatar answered Oct 11 '22 03:10

igogorek