F. Elliot
F. Elliot

Reputation: 238

pytest dynamically parametrized test

I have created a 'dynamic' list of parameters which I pass to parametrize.

OPTIONS = ['a', 'b', 'c']

def get_unique_pairs():
    unique_list = []
    for first in OPTIONS:
        for second OPTIONS:
            if first == second:
                continue
            unique_list.append({'first':first, 'second':second))
    return unique_list

def some_func()
    unique_pairs = get_unique_pairs()
    result = []
    for pair in unique_pair:
        if test(pair):
           continue
        else:
           result.append(pair)
    return pair

@pytest.mark.parametrize('param', some_fnc())
def test_fnc(param):
    first = param['first']
    second = param['second']

The input I wish to pass to test_fnc is [('a','b'),('a','c')...('c','b')] where the first and second elements are never the same. There is some additional logic I am using to further remove specific pairs.

When I run the test I get the output:

::test_fnc[param0] PASSED
::test_fnc[param1] PASSED
::test_fnc[param2] PASSED

I have two issues:

  1. I'm not entirely sure how to describe what I am doing to find further documentation / help.
  2. I would like more descriptive output (i.e. not param0), and I'd like to continue using dictionaries to pass data to the test.

Upvotes: 8

Views: 9564

Answers (2)

Paulo Scardine
Paulo Scardine

Reputation: 77251

I would write it like this:

import pytest
import itertools


OPTIONS = ['a', 'b', 'c']

@pytest.mark.parametrize(
    'param',
    itertools.permutations(OPTIONS, 2),
    ids=lambda pair: "first={}, second={}".format(*pair)
)
def test_fn(param):
    first, second = param
    assert first != second

Result:

> pytest --verbose 
================================ test session starts =================================
platform linux2 -- Python 2.7.12, pytest-3.6.1, py-1.5.3, pluggy-0.6.0 -- /usr/bin/python
cachedir: .pytest_cache
rootdir: /home/paulos/work/lixo/tests, inifile:
collected 6 items

test_foo.py::test_fn[first=a, second=b] PASSED                                     [ 16%]
test_foo.py::test_fn[first=a, second=c] PASSED                                     [ 33%]
test_foo.py::test_fn[first=b, second=a] PASSED                                     [ 50%]
test_foo.py::test_fn[first=b, second=c] PASSED                                     [ 66%]
test_foo.py::test_fn[first=c, second=a] PASSED                                     [ 83%]
test_foo.py::test_fn[first=c, second=b] PASSED                                     [100%]

================================ 6 passed in 0.03 seconds ================================

[update]

I think this answer is the solution I will use. Every day is a school day! (Out of curiosity is there a way I could be the 'param' into something like 'first, second' so to have test_foo(param) be test_foo(first, second). I'm not sure that actually helps anything... but I am curious – F. Elliot

If you don't mind a test_fn[a-b] instead of test_fn[a, b]:

@pytest.mark.parametrize(
    'first,second',
    itertools.permutations(OPTIONS, 2),
)
def test_fn(first, second):
    assert first != second

In practice we don't really run tests with --verbose anyway, so most of the time the output will be just a dot for each test.

Upvotes: 9

hoefling
hoefling

Reputation: 66171

  1. As Paulo Scardine mentioned in the comment, don't reinvent the wheel, use itertools.combinations.
  2. You are looking for the ids argument in parametrize hook. It's either list of names for each argument, or a function that accepts the argument and returns a string - repr is a simple solution for builtin types.

Combined example:

import itertools
import pytest


opts = ['a', 'b', 'c']


@pytest.mark.parametrize('param', itertools.combinations(opts, 2), ids=repr)
def test_fn(param):
    first = param[0]
    second = param[1]
    assert first != second

Upvotes: 5

Related Questions