2

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.

Abhijit Sarkar
  • 21,927
  • 20
  • 110
  • 219

2 Answers2

3

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]
wim
  • 338,267
  • 99
  • 616
  • 750
  • 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. – Abhijit Sarkar Mar 15 '21 at 03:16
  • Yes, the idfunc gets the parameter value only and appears to be unaware of the associated parameter name or any wider test context. – wim Mar 15 '21 at 03:20
  • 1
    Created https://github.com/pytest-dev/pytest/issues/8448. – Abhijit Sarkar Mar 15 '21 at 03:55
1

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]
Max
  • 11
  • 2
  • 1
    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. – Abhijit Sarkar Aug 04 '22 at 17:56
  • 2
    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 – joanis Aug 05 '22 at 00:15