parametrize.rst

Overview

This file provides comprehensive documentation and practical examples on how to perform test parametrization using the `pytest` testing framework. Parametrization in pytest enables running the same test function with different sets of input values, improving test coverage and reducing code duplication.

The documentation covers a wide range of scenarios and techniques for parametrizing tests, including:

This file is primarily instructional and serves as a guide for users to effectively use pytest's parametrization capabilities to write flexible, maintainable, and expressive test suites.


Detailed Explanations

Parametrizing Tests Based on Command Line Options

**Purpose:** Run a test multiple times with different parameter values that depend on a command line flag.

**Example:**

# test_compute.py
def test_compute(param1):
    assert param1 < 4
# conftest.py
def pytest_addoption(parser):
    parser.addoption("--all", action="store_true", help="run all combinations")

def pytest_generate_tests(metafunc):
    if "param1" in metafunc.fixturenames:
        if metafunc.config.getoption("all"):
            end = 5
        else:
            end = 2
        metafunc.parametrize("param1", range(end))

**Explanation:**

**Usage:**

$ pytest -q test_compute.py         # runs 2 test cases
$ pytest -q --all test_compute.py   # runs 5 test cases, last fails due to assertion

Customizing Test IDs in Parametrization

**Purpose:** Control how each parameter set is represented in test IDs, which helps in selective test runs and clearer failure reports.

**Key Points:**

**Example:**

import pytest
from datetime import datetime, timedelta

testdata = [
    (datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
    (datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]

# Default IDs generated by pytest
@pytest.mark.parametrize("a,b,expected", testdata)
def test_timedistance_v0(a, b, expected):
    assert a - b == expected

# Custom string IDs
@pytest.mark.parametrize("a,b,expected", testdata, ids=["forward", "backward"])
def test_timedistance_v1(a, b, expected):
    assert a - b == expected

# Function-based ID generation for datetime objects
def idfn(val):
    if isinstance(val, datetime):
        return val.strftime("%Y%m%d")

@pytest.mark.parametrize("a,b,expected", testdata, ids=idfn)
def test_timedistance_v2(a, b, expected):
    assert a - b == expected

# Using pytest.param with explicit IDs
@pytest.mark.parametrize(
    "a,b,expected",
    [
        pytest.param(datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1), id="forward"),
        pytest.param(datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1), id="backward"),
    ],
)
def test_timedistance_v3(a, b, expected):
    assert a - b == expected

Using pytest_generate_tests to Port TestScenarios

**Purpose:** Support parameterized tests based on scenarios defined as class-level data, commonly used in unittest extensions, now adapted for pytest.

**Example:**

def pytest_generate_tests(metafunc):
    idlist = []
    argvalues = []
    for scenario in metafunc.cls.scenarios:
        idlist.append(scenario[0])
        items = scenario[1].items()
        argnames = [x[0] for x in items]
        argvalues.append([x[1] for x in items])
    metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")

scenario1 = ("basic", {"attribute": "value"})
scenario2 = ("advanced", {"attribute": "value2"})

class TestSampleWithScenarios:
    scenarios = [scenario1, scenario2]

    def test_demo1(self, attribute):
        assert isinstance(attribute, str)

    def test_demo2(self, attribute):
        assert isinstance(attribute, str)

**Explanation:**


Deferring Setup of Parametrized Resources

**Purpose:** Avoid expensive resource setup during test collection by combining parametrization with fixtures.

**Example:**

# conftest.py
import pytest

def pytest_generate_tests(metafunc):
    if "db" in metafunc.fixturenames:
        metafunc.parametrize("db", ["d1", "d2"], indirect=True)

class DB1:
    pass

class DB2:
    pass

@pytest.fixture
def db(request):
    if request.param == "d1":
        return DB1()
    elif request.param == "d2":
        return DB2()
    else:
        raise ValueError("invalid internal test config")
# test_backends.py
def test_db_initialized(db):
    if db.__class__.__name__ == "DB2":
        pytest.fail("deliberately failing for demo purposes")

**Explanation:**


Indirect Parametrization

**Purpose:** Use fixtures to process or transform parameter values before running the test.

**Example:**

import pytest

@pytest.fixture
def fixt(request):
    return request.param * 3

@pytest.mark.parametrize("fixt", ["a", "b"], indirect=True)
def test_indirect(fixt):
    assert len(fixt) == 3

**Explanation:**


Applying Indirect Parametrization to Specific Arguments

**Example:**

import pytest

@pytest.fixture
def x(request):
    return request.param * 3

@pytest.fixture
def y(request):
    return request.param * 2

@pytest.mark.parametrize("x, y", [("a", "b")], indirect=["x"])
def test_indirect(x, y):
    assert x == "aaa"
    assert y == "b"

**Explanation:**


Parametrizing Test Methods via Class Configuration

**Example:**

import pytest

def pytest_generate_tests(metafunc):
    funcarglist = metafunc.cls.params[metafunc.function.__name__]
    argnames = sorted(funcarglist[0])
    metafunc.parametrize(
        argnames, [[funcargs[name] for name in argnames] for funcargs in funcarglist]
    )

class TestClass:
    params = {
        "test_equals": [dict(a=1, b=2), dict(a=3, b=3)],
        "test_zerodivision": [dict(a=1, b=0)],
    }

    def test_equals(self, a, b):
        assert a == b

    def test_zerodivision(self, a, b):
        with pytest.raises(ZeroDivisionError):
            a / b

**Explanation:**


Parametrization with Multiple Fixtures

**Purpose:** Test serialization/deserialization with various Python interpreters and objects.

**Description:** The file includes a real-life multipython example (not shown here) that runs tests combining multiple interpreters and objects, skipping tests if interpreters are missing.


Parametrizing Optional Implementations or Imports

**Example:**

# conftest.py
import pytest

@pytest.fixture(scope="session")
def basemod(request):
    return pytest.importorskip("base")

@pytest.fixture(scope="session", params=["opt1", "opt2"])
def optmod(request):
    return pytest.importorskip(request.param)
# base.py
def func1():
    return 1
# opt1.py
def func1():
    return 1.0001
# test_module.py
def test_func1(basemod, optmod):
    assert round(basemod.func1(), 3) == round(optmod.func1(), 3)

**Explanation:**


Setting Marks or Test IDs for Individual Parameter Sets

**Example:**

import pytest

@pytest.mark.parametrize(
    "test_input,expected",
    [
        ("3+5", 8),
        pytest.param("1+7", 8, marks=pytest.mark.basic),
        pytest.param("2+4", 6, marks=pytest.mark.basic, id="basic_2+4"),
        pytest.param("6*9", 42, marks=[pytest.mark.basic, pytest.mark.xfail], id="basic_6*9"),
    ],
)
def test_eval(test_input, expected):
    assert eval(test_input) == expected

**Explanation:**


Parametrizing Conditional Exception Raising

**Example:**

from contextlib import nullcontext
import pytest

@pytest.mark.parametrize(
    "example_input,expectation",
    [
        (3, nullcontext(2)),
        (2, nullcontext(3)),
        (1, nullcontext(6)),
        (0, pytest.raises(ZeroDivisionError)),
    ],
)
def test_division(example_input, expectation):
    with expectation as e:
        assert (6 / example_input) == e

**Explanation:**


Implementation Details and Algorithms

Interaction with Other Parts of the System


Usage Summary


Visual Diagram of File Structure and Relationships

flowchart TD
    A[pytest_generate_tests hook] -->|generates| B[Parametrized test cases]
    B --> C{Parametrization Types}
    C --> D[Direct parameters]
    C --> E[Indirect parameters (fixtures)]
    C --> F[Class-level params dict]
    C --> G[Scenario-based params]

    E --> H[Fixtures with request.param]
    G --> I[Scenario tuples: (id, dict)]

    B --> J[Custom test IDs]
    B --> K[pytest.param with marks and IDs]

    L[Command Line Options] --> A
    M[Markers] --> K
    N[Optional Imports] --> H

    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:1px
    style C fill:#afa,stroke:#333,stroke-width:1px

*Diagram Explanation:*


This documentation equips users with both conceptual understanding and practical code snippets to master pytest parametrization for robust and maintainable testing workflows.