10

I have a parameterized test which takes str, and dict as an argument and so the name look pretty weird if I allow pytest to generate ids.

I want to generate custom ids using a function, however it seems it's not working as intended.

def id_func(param):
    if isinstance(param, str):
        return param


@pytest.mark.parametrize(argnames=('date', 'category_value'),
                         argvalues=[("2017.01", {"bills": "0,10", "shopping": "100,90", "Summe": "101,00"}),
                                    ("2017.02", {"bills": "20,00", "shopping": "10,00", "Summe": "30,00"})],
                         ids=id_func)
def test_demo(date, category_value):
    pass

I was thinking it would return something like this

test_file.py::test_demo[2017.01] PASSED
test_file.py::test_demo[2017.02] PASSED

but it's returning this.

test_file.py::test_demo[2017.01-category_value0] PASSED
test_file.py::test_demo[2017.02-category_value1] PASSED

Could someone tell me what's wrong with this, or is there any way to achieve this?

Update:

I realize what's the issue, if_func will be called for each parameter and if I won't return str for any parameter default function will be called. I have fix but that's also ugly.

def id_func(param):
    if isinstance(param, str):
        return param
    return " "

Now it returns something like this,

test_file.py::test_demo[2017.01- ] PASSED
test_file.py::test_demo[2017.02- ] PASSED

The problem is even If I return empty string (i.e. return "" )it takes the default representation. Could someone let me know why?

lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
Gaurang Shah
  • 11,764
  • 9
  • 74
  • 137

3 Answers3

8

One way is to move your argvalues to another variable and write your test like this:

import pytest


my_args = [
      ("2017.01", {"bills": "0,10", "shopping": "100,90", "Summe": "101,00"}),
      ("2017.02", {"bills": "20,00", "shopping": "10,00", "Summe": "30,00"})
]


@pytest.mark.parametrize(
    argnames=('date', 'category_value'), argvalues=my_args,
    ids=[i[0] for i in my_args]
)
def test_demo(date, category_value):
    pass

Test execution:

$ pytest -v tests.py 
================= test session starts =================
platform linux2 -- Python 2.7.12, pytest-3.2.1, py-1.4.34, pluggy-0.4.0 -- /home/kris/.virtualenvs/2/bin/python2
cachedir: .cache
rootdir: /home/kris/projects/tmp, inifile:
collected 2 items                                      

tests.py::test_demo[2017.01] PASSED
tests.py::test_demo[2017.02] PASSED

============== 2 passed in 0.00 seconds ===============

I think it's not possible with a function (idfn in your case), because if it's not generating label for an object the default pytest representation is used.
Check pytest site for details.

kchomski
  • 2,872
  • 20
  • 31
3

Usually, when I want to be specific about test case being executing in the params I use named tuples as a workaround for the id funcion being executed once per param, that way a get a cleaner test description.

import pytest
from collections import namedtuple


TCase = namedtuple("TCase", "x,y,expected,description")


test_cases = [
    TCase(10, 10, 20, "10 + 10 should be 20"),
    TCase(1, 1, 2, "1 + 1 should be 2"),
]


def idfn(tc: TCase):
    return tc.description


@pytest.mark.parametrize("tc", test_cases, ids=idfn)
def test_sum(tc):
    assert tc.x + tc.y == tc.expected

Output:

example.py::test_sum[10 + 10 should be 20] PASSED                                                                           
example.py::test_sum[1 + 1 should be 2] PASSED 

So I would write your example as:

from collections import namedtuple
import pytest


TCase = namedtuple("TCase", "date,data")

my_args = [
    TCase("2017.01", {"bills": "0,10", "shopping": "100,90", "Summe": "101,00"}),
    TCase("2017.02", {"bills": "20,00", "shopping": "10,00", "Summe": "30,00"}),
]


@pytest.mark.parametrize("tc", my_args, ids=lambda tc: tc.date)
def test_demo(tc):
    # Do something in here with tc.date and tc.data
    pass

Output:

migration.py::test_demo[2017.01] PASSED                                                                                       
migration.py::test_demo[2017.02] PASSED 
Raydel Miranda
  • 13,825
  • 3
  • 38
  • 60
2

Alternatively you can also use a list comprehension to generate your ids as follows:

import pytest


values = [
    ("2017.01", {"bills": "0,10", "shopping": "100,90", "Summe": "101,00"}),
    ("2017.02", {"bills": "20,00", "shopping": "10,00", "Summe": "30,00"})
]
value_ids = [e[0] for e in values]


@pytest.mark.parametrize('date,category_value', values, ids=value_ids
)
def test_demo(date, category_value):
    pass

Assuming these tests are in test_file.py at the root of your directory, if you run pytest test_file.py --co -q, you will get the following output:

test_file.py::test_demo[2017.01]
test_file.py::test_demo[2017.02]
lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228