16

I have an array of objects that I need to run for each test inside my test class. I want to parametrize every test function in a TestClass. The end goal is to have something resembling:

@pytest.mark.parametrize('test_input', [1, 2, 3, 4])
class TestClass:
    def test_something1(self, test_input):
        # test code here, runs each time for the parametrize

But from my understanding, you can't pass input parameters, or at least call @pytest.mark.parametrize on a class, those markers are meant for defs not classs

What I have now:

class TestClass:
    def test_something1(self):
        for i in stuff:
            # test code here

    def test_something2(self):
        for i in stuff:
            # test code here
    ...

Is there a way to pass the parametrize a class itself or every function inside the TestClass? Maybe a @pytest.mark.parametrize inside a @pytest.fixture...(autouse=True).

I want to keep my tests organized as a class because it mirrors the file that I'm testing. Because I loop through these objects in at least a dozen different tests, it would be easier to call a loop of the class than in each def.

lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
SkylerHill-Sky
  • 2,106
  • 2
  • 17
  • 33

2 Answers2

38

You CAN apply parametrize to classes. From the docs:

@pytest.mark.parametrize allows one to define multiple sets of arguments and fixtures at the test function or class.

The same data will be sent to all test methods in the class.

@pytest.mark.parametrize('test_input', [1, 2, 3, 4])
class TestClass:
    def test_something1(self, test_input):
        pass


    def test_something2(self, test_input):
        pass

If you run the test with the following using pytest -v you will have the following output:

=========================================================================================== test session starts ============================================================================================
platform darwin -- Python 3.7.3, pytest-4.4.2, py-1.8.0, pluggy-0.11.0 -- /Users/user/.local/share/virtualenvs/stack-overlfow-pycharm-L-07rBZ9/bin/python3.7
cachedir: .pytest_cache
rootdir: /Users/user/Documents/spikes/stack-overlfow-pycharm
collected 8 items

test_class.py::TestClass::test_something1[1] PASSED                                                                                                                                                  [ 12%]
test_class.py::TestClass::test_something1[2] PASSED                                                                                                                                                  [ 25%]
test_class.py::TestClass::test_something1[3] PASSED                                                                                                                                                  [ 37%]
test_class.py::TestClass::test_something1[4] PASSED                                                                                                                                                  [ 50%]
test_class.py::TestClass::test_something2[1] PASSED                                                                                                                                                  [ 62%]
test_class.py::TestClass::test_something2[2] PASSED                                                                                                                                                  [ 75%]
test_class.py::TestClass::test_something2[3] PASSED                                                                                                                                                  [ 87%]
test_class.py::TestClass::test_something2[4] PASSED                                                                                                                                                  [100%]

========================================================================================= 8 passed in 0.03 seconds =========================================================================================

and this is exactly what you want.

lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
  • Using this syntax, is it possible to parametrize the setup_method(), too? It seems that parameters don't apply to setup_method. Possibly, it searches for methods with a test_ prefix. – Thanasis Mattas Oct 14 '20 at 10:04
  • 4
    This unfortunately does not work if your test classes inherits from `unittest.TestCase`, which might be needed in some cases. – spookylukey May 24 '21 at 09:04
  • 1
    I was looking to this solution, but here each test func. runs for provided input param then execution goes to other test function. Is is posible the for 1s iteration, all func. of the class runs and then 2nd iteration starts, something like: test_something1[1] test_something2[1] test_something1[2] test_something2[2] – Dcode Oct 23 '21 at 10:14
  • @Dcode Late response, but it is universally considered to be a really bad idea for tests to be sensitive to the order of their execution, or to be dependent on any other test functions or methods having to run at all. If one test sets up state that another test needs, consider either combining them into one long-running sequential "and then the user does THIS" test, or (probably better in the long run) create setup_X() functions that each test can call to set up the initial state it needs to run in isolation. – Jonathan Hartley Mar 09 '23 at 03:12
4

I have solved it. I was overcomplicating it; instead of using a mark I can use a fixture function that passes in parameters.

Before I found the answer (Without parametrize):

class TestClass:
    def test_something(self):
        for i in example_params:
            print(i)

Answer, using pytest fixture. Will do the same thing, but just need input, not a for loop:

import pytest
example_params = [1, 2, 3]

@pytest.fixture(params=example_params)
def param_loop(request):
    return request.param

class TestClass:
    def test_something(self, param_loop):
        print(param_loop)

So, to parametrize all defs:

  1. Use the @pytest.fixture(params=[]) decorator on a def my_function(request)
  2. Inside my_function, return request.param
  3. Add the my_function to the inputs of any function that you want to parametrize
SkylerHill-Sky
  • 2,106
  • 2
  • 17
  • 33
  • I stumbled upon your comment, as I was looking exactly for this, however, if I try to implement the test the way you explained it I get the error: `Failed: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.` and if I put `example_params` in the class I get pass the error, but then I get the error that `param_loop` is missing. Have you had the same issues? @Comradsky – dsax7 Apr 17 '17 at 20:29
  • See also [here](https://stackoverflow.com/questions/17434031/py-test-parametrizing-test-classes/22417893#22417893) and [here](https://docs.pytest.org/en/latest/fixture.html#fixture-parametrize) – Christian Long Jul 05 '18 at 16:39
  • It's possible to use `parametrize` function with specific method of your `class TestMyCustomTestCase`and avoid this workaround. – cpinamtz Jan 08 '20 at 08:50
  • As with .parametrize(), this solution does not work for me on test classes that inherit from `unittest.TestCase`. – Jonathan Hartley Mar 09 '23 at 03:20