Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pytest parametrize I am getting missing required positional arguments

Tags:

python

pytest

I try to add use pytest parametrize for my unit tests. But I see the error of "missing required positional arguments". Can you help where the issue is? Thanks.

 @pytest.mark.parametrize("param", ["a", "b"])
    def test_pytest(self, param):
        print(param)
        assert False

I get the following exception:

class Tests(unittest.TestCase):
@contextlib.contextmanager
def testPartExecutor(self, test_case, isTest=False):
    old_success = self.success
    self.success = True
    try:
>       yield

           with outcome.testPartExecutor(self):
                self.setUp()
            if outcome.success:
                outcome.expecting_failure = expecting_failure
                with outcome.testPartExecutor(self, isTest=True):
>                   testMethod()
E                   TypeError: test_pytest() missing 1 required positional argument: 'param'
like image 699
susanne Avatar asked Jan 19 '26 14:01

susanne


1 Answers

As quoted in a comment to the question from pytest's documentation, pytest's parametrization does not work for methods under unittest.TestCase [sub]classes. I can think of several approaches to overcome this issue:

First approach - find someone else who already solved this issue

One example for this would be the parameterized package, which can be used as follows:

import unittest
from parameterized import parameterized

class TestClass(unittest.TestCase):

    def test_ok(self):
        assert True

    @parameterized.expand((("a",), ("b",)))
    def test_single(self, param):
        print(param)
        assert False

    @parameterized.expand((("a", "c", "d"), ("b", "c", "d")))
    def test_multiple(self, first, second, third):
        self.assertEqual(second, "c")
        self.assertEqual(third, "d")
        self.assertTrue(first in ("a", "b"))
        self.fail(f"Failing test for {first}_{second}_{third}")

pytest will handle the code above as expected, resulting with the following summary:

====================================== short test summary info ======================================
FAILED test.py::TestClass::test_multiple_0_a - AssertionError: Failing test for a_c_d
FAILED test.py::TestClass::test_multiple_1_b - AssertionError: Failing test for b_c_d
FAILED test.py::TestClass::test_single_0_a - assert False
FAILED test.py::TestClass::test_single_1_b - assert False
==================================== 4 failed, 1 passed in 0.06s ====================================

Second approach - create explicit test for each param

While this scales-up quite poorly (e.g. when testing for more than a couple of values or parameters), explicit declarations avoid introducing a new 3rd-party package.
The more cumbersome approach with explicit methods:

import unittest

class TestClass(unittest.TestCase):

    def _test_param(self, param):
        self.assertEqual(param, list(range(5)))

    def test_0_to_4(self):
        return self._test_param([0, 1, 2, 3, 4])

    def test_sort_list(self):
        return self._test_param(sorted([0, 3, 2, 1, 4]))

    def test_fail(self):
        return self._test_param([0, 3, 2, 1, 4])

And a leaner approach using partialmethods:

import unittest
from functools import partialmethod

class TestClass(unittest.TestCase):

    def _test_param(self, param):
        self.assertEqual(param, list(range(5)))

    test_0_to_4 = partialmethod(_test_param, [0, 1, 2, 3, 4])
    test_sort_list = partialmethod(_test_param, sorted([0, 3, 2, 1, 4]))
    test_fail = partialmethod(_test_param, [0, 3, 2, 1, 4])

While these would yield different tracebacks, pytest's summary will be the same for both:

========================================= short test summary info =========================================
FAILED test.py::TestClass::test_fail - AssertionError: Lists differ: [0, 3, 2, 1, 4] != [0, 1, 2, 3, 4]
======================================= 1 failed, 2 passed in 0.02s =======================================

Third approach - hack it yourself

As an elegant decorator interface can already be found with the first method, I will demonstrate a hacky, clunky, unreusable, but fairly quick DIY solution; injecting the class with "dynamic" test methods, attaching the parameters as kwargs to each method:

import unittest

class TestClass(unittest.TestCase):

    def test_ok(self):
        assert True

    def _test_injected_method(self, first, second):
        self.assertEqual(second, "z", f"{second} is not z")
        self.assertTrue(first in ("x", "y"), f"{first} is not x or y")
        self.fail(f"Failing test for {first}_{second}")

for param1 in ("w", "x", "y"):
    for param2 in ("z", "not_z"):
        def _test(self, p1=param1, p2=param2):
            return self._test_injected_method(p1, p2)
        _test.__name__ = f"test_injected_method_{param1}_{param2}"
        setattr(TestClass, _test.__name__, _test)

This will also work with pytest, yielding the following summary:

========================================= short test summary info =========================================
FAILED test.py::TestClass::test_injected_method_w_not_z - AssertionError: 'not_z' != 'z'
FAILED test.py::TestClass::test_injected_method_w_z - AssertionError: False is not true : w is not x or y
FAILED test.py::TestClass::test_injected_method_x_not_z - AssertionError: 'not_z' != 'z'
FAILED test.py::TestClass::test_injected_method_x_z - AssertionError: Failing test for x_z
FAILED test.py::TestClass::test_injected_method_y_not_z - AssertionError: 'not_z' != 'z'
FAILED test.py::TestClass::test_injected_method_y_z - AssertionError: Failing test for y_z
======================================= 6 failed, 1 passed in 0.02s =======================================

IMHO this approach is less elegant than the second (which IMO is less elegant than the first), but if introducing 3rd-party packages is not an option, this approach scales-up more efficiently than the second.

Other approaches

  • Refactoring test classes to not inherit from unittest.TestCase is an option, but quite a risky one; irregardless of technical efforts required for this task (e.g. replacing all self.assert* calls with assert statements, handling setUp and tearDown methods, etc.), there is also an implicit risk where tests could be silently ignored unless explicitly invoked with pytest.
  • Other solutions are suggested here and there, but I believe most of them address nose rather than pytest. Some answers suggest using metaclasses, and while I have a feeling that's an overkill of a solution to this specific problem, if my previous suggestions do not fit your use-case - that's something worth looking into.
like image 60
micromoses Avatar answered Jan 21 '26 06:01

micromoses



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!