Skip to main content

Parametrize & Markers

@pytest.mark.parametrize eliminates copy-pasted tests by generating multiple test cases from a single function. Markers let you categorise, skip, and xfail individual tests.

@pytest.mark.parametrize

import pytest

def is_palindrome(word):
    return word == word[::-1]

@pytest.mark.parametrize("word,expected", [
    ("racecar", True),
    ("hello",   False),
    ("level",   True),
    ("python",  False),
    ("noon",    True),
])
def test_palindrome(word, expected):
    assert is_palindrome(word) == expected

pytest generates 5 separate test cases — each appears independently in the output:

PASSED test_strings.py::test_palindrome[racecar-True]
PASSED test_strings.py::test_palindrome[hello-False]
PASSED test_strings.py::test_palindrome[level-True]

Multiple Parameters

@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
])
def test_add(a, b, expected):
    assert a + b == expected

Parametrize Visualiser

Parametrize test generation
Ctrl+Enter
HTML
CSS
JS
Preview

Built-in Markers

# Skip a test
@pytest.mark.skip(reason="Not implemented yet")
def test_export():
    pass

# Skip conditionally
import sys
@pytest.mark.skipif(sys.platform == "win32", reason="Linux only")
def test_file_permissions():
    pass

# Expected failure
@pytest.mark.xfail(reason="Known bug #123")
def test_edge_case():
    assert my_func(None) == ""   # currently raises TypeError

# Slow test  requires --runslow flag
@pytest.mark.slow
def test_full_export():
    pass

Custom Markers

Register custom markers in pytest.ini or pyproject.toml to avoid warnings:

# pytest.ini
[pytest]
markers =
    slow: marks tests as slow (deselect with -m "not slow")
    integration: marks tests that hit real external services
    smoke: marks critical path tests

Run subsets:

pytest -m smoke          # only smoke tests
pytest -m "not slow"     # skip slow tests
pytest -m "integration or smoke"