If you’re a developer using TDD (test-driven development), you’re familiar with the importance of Unit Tests.

What Are Pytest Parameters?

By using decorators such as @pytest.mark.parametrize, you can specify the inputs and expected outputs for your test cases.

OK, imagine you had 1000 unit tests, each one with 10 inputs. Now that would mean writing a LOT OF code.

Pytest parameterized tests allow you to define a wide range of inputs and apply them to your tests.

Difference Between Pytest Fixtures And Parameters

In summary, fixtures are used for test setup and resource management, while parameters enable data-driven testing.

# Test Math Functions    
@pytest.mark.parametrize( "a, b, expected",    
    [    
        (1, 2, 3),    
        (5, -1, 4),    
    ],)    
def test_addition(a, b, expected):    
    assert addition(a, b) == expected

In the above decorator, we define 2 arguments a and b and the expected response.

We then proceed to define a list containing tuples where each element of the tuple is a value of a , b and expected (in that order).

You can include as many tuples as you wishn within the list. Pytest will then execute a test session for each of the input arguments.

Parameterizing Classes

Similar to functions, we can pass parameters to classes. Here’s a simple example.

"""  
Test Class Parameterization.  
"""  
from src.examples import Greeting  
import pytest
  
@pytest.mark.parametrize( "name",  
    [  
        "John Doe",  
        "Jane Doe",  
        "Foo Bar",  
    ],)  
class TestGreeting:  
    """Test the Greeting class."""  
  
    def test_say_hello(self, name):  
        """Test the say_hello method."""  
        greeting = Greeting(name)  
        assert greeting.say_hello() == f"Hello {name}"  
  
    def test_say_goodbye(self, name):  
        """Test the say_goodbye method."""  
        greeting = Greeting(name)  
        assert greeting.say_goodbye() == f"Goodbye {name}"
 

Generating Test Input Data

Using range()

# Parameterized testing with 2 arguments and ranges  
@pytest.mark.parametrize("a", range(5))  
@pytest.mark.parametrize("b", range(10))  
def test_addition_2_args(a, b):  
    assert addition(a, b) == a + b
 

pytest-parameterized-tests-range

Using pytest_generate_tests

Appreciate that all test inputs are not integers and the range() function is less useful in other cases.

Pytest also has an inbuilt hook called pytest_generate_tests which receives the metafunc object as an argument.

The metafunc object provides information about the test function, such as its name, the arguments it takes, and the fixtures it requires.

Although this works, I find using the @pytest.mark.parametrize marker way cleaner and more convenient.

Apply Markers To Individual Tests When Using Parameters

How about if you want to use Pytest parameters, but still use markers like xfail or skip test?

# Simple test to demo how to mark a pytest parameter test  
@pytest.mark.parametrize( "string, expected",  
    [  
        (" hello ", "hello"),  
        (" WoRlD ", "world"),  
        pytest.param(None, 42, marks=pytest.mark.xfail(reason="some bug")),  
    ],)  
def test_clean_string_marked(string, expected):  
    assert clean_string(string) == expected

pytest-parameterized-tests-xfail-marker