3

I've the following tests:

@pytest.mark.parametrize(
    "nums",
    [[3, 1, 5, 4, 2], [2, 6, 4, 3, 1, 5], [1, 5, 6, 4, 3, 2]]
)
def test_cyclic_sort(nums):
    pass


@pytest.mark.parametrize(
    "nums, missing",
    [([4, 0, 3, 1], 2)]
)
def test_find_missing_number(nums, missing):
    pass

I'd like to customize the test names to include the input array. I've read the pytest docs, and this question and this question, but none answer the following questions:

  1. What is passed to the id func? In my code above, the first test takes one parameter, the second takes two.
  2. pytest docs use a top-level function for id, whereas I'd like to put my tests in a class and use a @staticmethod. Trying to reference the static method with TestClass.static_method from inside TestClass gives an error in PyCharm; what is the correct syntax for doing this?

Edit: Created https://github.com/pytest-dev/pytest/issues/8448.

3 Answers 3

4

When using a callable for the ids keyword, it will be called with a single argument: the value of the test parameter being parametrized. The callable ids return a string, which will be used in square brackets as the test name suffix.

If the test is parametrizing over multiple values, the function will still be called with a single argument, but it will be called multiple times per test. The generated name will be joined with dashes, something like

"-".join([idfunc(val) for val in parameters])

For example:

test_something[val1-val2-val3]

Here is the join in the pytest source.

To use a static method, this syntax works:

class TestExample:

    @staticmethod
    def idfunc(val):
        return f"foo{val}"

    @pytest.mark.parametrize(
        "x, y",
        [
            [1, 2],
            ["a", "b"],
        ],
        ids=idfunc.__func__,
    )
    def test_vals(self, x, y):
        assert x
        assert y

This will generate two tests, calling idfunc four times as described above.

TestExample::test_vals[foo1-foo2]
TestExample::test_vals[fooa-foob]
Sign up to request clarification or add additional context in comments.

3 Comments

So, in my example, I can't use the same idfunc for both tests because the method has no way to differentiate between invocations from test_cyclic_sort and test_find_missing_number? In other words, when idfunc is called with 2 for test_find_missing_number, there's nothing it can do to ignore it. Quite contrived apparently.
Yes, the idfunc gets the parameter value only and appears to be unaware of the associated parameter name or any wider test context.
2

I like wims answer, and this is intended as a comment to his answer (I dont have the points to make a comment). This seems more pythonic to me. It also helps avoid using a static method.

class TestExample:
    @pytest.mark.parametrize(
        "x, y",
        [
            [1, 2],
            ["a", "b"],
        ],
        ids= lamba val : f"foo{val}"
    )
    def test_vals(self, x, y):
        assert x
        assert y

This will have the same output:

TestExample::test_vals[foo1-foo2]
TestExample::test_vals[fooa-foob]

2 Comments

Why do you think using a lambda is “more Pythonic” than a static fn? Is there a style guide that you can quote here, because your opinion alone isn’t fact, opinions differ.
I know it's a matter of opinion, @AbhijitSarkar, but this seems like a fine place for a lambda function: it's only used in one place, passed as a parameter to a function. And using it makes the code shorter yet not less legible (IMO). Whether it's more pythonic or not, you're right, is up to debate, I but I like this alternate solution. +1
2

I keep ending up here, so for posterity I want to note that you can also use the pytest.param objects (https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest-param) to directly associate ids with test cases.

E.g.

cases = [
    pytest.param(1, 1, id="first"),
    pytest.param(2, 4, id="second"),
    pytest.param(3, 9, id="third"),
]

@pytest.mark.parametrize("x, expected", cases)
def test_mytest(x, expected):
    assert x**2 == expected

Output:

test_mytest[first] PASSED           [ 33%]
test_mytest[second] PASSED          [ 66%]
test_mytest[third] PASSED           [100%]

You can also add marks to test cases this way, for example if some of the cases are "slow" you can just add marks=slow into those params objects.

2 Comments

This would be very error-prone and verbose. What we want is for pytest to simply distinguish between each execution without needing the user to assign a random string for each line item. Sadly, this pytest can't do.
It depends on the situation, sometimes it is perfectly useful to just hand-write something meaningful for the id, and it is much less error prone to do that right next to the parameters rather than in a separate list. If you prefer something programmatic that's fine, and you can do that with this approach as well. It's just an option I don't see used much but have found pretty useful, especially in conjunction with adding marks to specific test cases.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.