Renaming parameters in pytest.mark.parametrize

465 views Asked by At

I have a code which uses files in a directory as parameters:

def get_testcases(directory):
    files = list(os.listdir(directory))
    testcases = filter(lambda x: x.endswith('.yaml'), files)
    for testcase in testcases:
        postconf = testcase.replace('.yaml', '.conf')
        yield (
            os.path.join(directory, testcase),
            os.path.join(directory, postconf)
        )

def get_pre_configs(directory):
    for file in os.listdir(directory):
        if file.endswith('.conf'):
            yield os.path.join(directory, file)

@pytest.mark.parametrize("pre_config", get_pre_configs('pre_configs'))
@pytest.mark.parametrize("testcase_declaration, testcase_result", get_testcases('testcases'))
def test_foo(pre_config, testcase_declaration, testcase_result):
    assert testcase_declaration
    assert testcase_result
    assert pre_config

It works as I need, but I don't like pytest output:

test_interface.py::test_foo[testcases/up.yaml-testcases/up.conf-pre_configs/bad.conf] PASSED              [ 16%]
test_interface.py::test_foo[testcases/up.yaml-testcases/up.conf-pre_configs/simple.conf] PASSED           [ 33%]
test_interface.py::test_foo[testcases/up.yaml-testcases/up.conf-pre_configs/complicated.conf] PASSED      [ 50%]
test_interface.py::test_foo[testcases/down.yaml-testcases/down.conf-pre_configs/bad.conf] PASSED          [ 66%]
test_interface.py::test_foo[testcases/down.yaml-testcases/down.conf-pre_configs/simple.conf] PASSED       [ 83%]
test_interface.py::test_foo[testcases/down.yaml-testcases/down.conf-pre_configs/complicated.conf] PASSED  [100%]

Is there any way to show a different name for the test than the value passed to the test? I want to trim away a directory name and an extension from filenames (for test names only, I'd like to pass them 'as is' to the test).

1

There are 1 answers

1
Pierre D On BEST ANSWER

It turns out that @pytest.mark.parametrize (as well as @pytest.fixtures) are quite powerful. They allow you to change the name of each test by specifying an ids list. The trick is to generate the arguments for parametrize dynamically.

I refactored your code (see further below). Given a local dir containing:

$ find . -type f -name '*.yaml' -o -name '*.conf'
./pre_configs/yikes.conf
./pre_configs/foobar.conf
./testcases/hello.yaml
./testcases/world.yaml

Then the pytest output is:

collecting ... collected 4 items

test_foo.py::test_foo[yikes-hello] PASSED                                [ 25%]
test_foo.py::test_foo[yikes-world] PASSED                                [ 50%]
test_foo.py::test_foo[foobar-hello] PASSED                               [ 75%]
test_foo.py::test_foo[foobar-world] PASSED                               [100%]

============================== 4 passed in 0.19s ===============================

Here is the refactored code. Note how both get_testcases() and get_pre_configs() both return a dict that can be used as kwargs for @pytest.mark.parametrize. In particular, ids allows you to override the name used by pytest.

def getfiles(directory, ext):
    """return two lists: fullpath and names (without extension)"""
    n = len(ext)
    paths, names = zip(*[
        (ent.path, ent.name[:-n])
        for ent in os.scandir(directory)
        if ent.is_file() and ent.name.endswith(ext)])
    return paths, names


def get_testcases(directory):
    ypaths, names = getfiles(directory, '.yaml')
    cpaths = [f'{os.path.splitext(s)[0]}.conf' for s in ypaths]
    return {
        'argnames': ['testcase_declaration', 'testcase_result'],
        'argvalues': zip(ypaths, cpaths),
        'ids': names}


def get_pre_configs(directory):
    paths, names = getfiles(directory, '.conf')
    return {
        'argnames': ['pre_config'],
        'argvalues': zip(paths),  # always wants a list of tuples
        'ids': names}


@pytest.mark.parametrize(**get_pre_configs('pre_configs'))
@pytest.mark.parametrize(**get_testcases('testcases'))
def test_foo(pre_config, testcase_declaration, testcase_result):
    assert os.path.isfile(pre_config)
    assert os.path.isfile(testcase_declaration)
    assert testcase_result