Skip to content

Python · Testing

Python pytest Explained — assert, Fixtures, Parametrize, and Testing Exceptions

4 min read Updated 2026-06-19 Share:

Practice pytest Essentials interview questions

Python pytest, explained

pytest is the de facto standard for testing Python. It wins over the stdlib unittest by being far less boilerplate: plain assert statements, function-based tests, and a powerful fixture system. Here are the essentials that cover the vast majority of real test suites.

Tests are just functions with assert

A pytest test is a function named test_* that uses a bare assert. pytest rewrites assertions to show exactly what went wrong — no special assert methods needed.

# test_math.py
def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
pytest                 # auto-discovers test_*.py and test_* functions
pytest -v              # verbose, one line per test
pytest test_math.py::test_add   # run one test

On failure pytest prints the actual values (assert 6 == 5), so you rarely need a custom message.

Fixtures for setup and teardown

A fixture provides reusable setup. Declare it with @pytest.fixture and request it by naming it as a test argument — pytest injects the return value. Anything after yield is teardown.

import pytest

@pytest.fixture
def db():
    conn = connect(":memory:")     # setup
    yield conn                     # value handed to the test
    conn.close()                   # teardown, runs after the test

def test_insert(db):               # pytest injects the fixture
    db.execute("INSERT ...")
    assert db.count() == 1

This replaces setUp/tearDown with composable, explicit dependencies — a test only gets the fixtures it actually names.

Fixture scope

By default a fixture runs per test (function scope). For expensive setup (a database, a server), widen the scope so it's created once and reused.

@pytest.fixture(scope="module")    # created once per test module
def server():
    s = start_server()
    yield s
    s.stop()

Scopes are function, class, module, package, and session. Put shared fixtures in a conftest.py and pytest makes them available to all tests in that directory tree automatically.

Parametrize for table-driven tests

@pytest.mark.parametrize runs the same test across many inputs, each reported as a separate case — far better than a loop, which stops at the first failure.

import pytest

@pytest.mark.parametrize("value, expected", [
    (2, 4),
    (3, 9),
    (-4, 16),
])
def test_square(value, expected):
    assert value ** 2 == expected

Each tuple becomes an independent test, so you see all failures at once with the exact input that broke.

Testing that code raises

Use pytest.raises as a context manager to assert an exception is raised — and inspect it.

import pytest

def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError):
        1 / 0

def test_message():
    with pytest.raises(ValueError, match="invalid"):   # regex on the message
        int("abc")
    # or capture it:
    with pytest.raises(ValueError) as exc:
        raise ValueError("boom")
    assert "boom" in str(exc.value)

The match= argument checks the exception message against a regex in one line.

Markers and useful flags

Markers tag tests for selection or special handling, and pytest has flags that speed up the edit-test loop.

import pytest

@pytest.mark.slow
def test_big_job(): ...

@pytest.mark.skip(reason="not implemented")
def test_future(): ...

@pytest.mark.xfail            # expected to fail
def test_known_bug(): ...
pytest -k "add and not slow"   # run by name expression
pytest -m slow                 # run only tests marked slow
pytest -x --lf                 # stop at first failure, rerun last-failed

Recap

pytest tests are plain test_* functions using bare assert with rich failure output. Fixtures (@pytest.fixture, request by argument name, yield for teardown) provide composable setup, and scope plus conftest.py control sharing. Use @pytest.mark.parametrize for table-driven tests that report every case independently, and pytest.raises (with match=) to assert and inspect exceptions. Markers and flags like -k, -x, and --lf make running the right tests fast.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel