AtomLearnAtomLearn
DashboardGoalsPathAchievementsReviewSign In
DashboardTestingpytest: Fixtures & Parametrize
TestingNot Started

pytest: Fixtures & Parametrize

Reusable fixtures, parametrized tests, marks, and effective test organization

0%
Knowledge0%
Learn & Drill
Fluency0%
Drill & Speed
Retention0%
Mastery & Review
Confidence0%
All modes
Practice
Knowledge
Fluency
Retention

Knowledge Debt detected

You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.

Explanation

pytest's power comes from fixtures (shared setup/teardown) and parametrize (running one test against many inputs).

Fixtures — reusable setup:

python import pytest @pytest.fixture def sample_user(): return {"id": 1, "name": "Alice", "active": True} def test_user_name(sample_user): assert sample_user["name"] == "Alice" def test_user_is_active(sample_user): assert sample_user["active"] is True pytest injects the fixture by matching the parameter name — no import needed. Each test gets a fresh call to sample_user()`.

Fixture with teardown (using `yield`):

python @pytest.fixture def db_connection(): conn = create_connection("test.db") yield conn # test runs here conn.close() # teardown after test os.remove("test.db") Everything before yield` is setup; everything after is teardown. pytest guarantees teardown runs even if the test fails.

Fixture scopes:

python @pytest.fixture(scope="module") # runs once per test file def expensive_resource(): return load_large_dataset() Scopes: function (default, once per test), module (once per file), session` (once per full test run).

Parametrize — one test, many inputs:

python @pytest.mark.parametrize("a, b, expected", [ (2, 3, 5), (0, 0, 0), (-1, 1, 0), (100, -50, 50), ]) def test_add(a, b, expected): assert add(a, b) == expected `` pytest runs the test four times, once per tuple. Each is a separate test case in the output — failures show exactly which input set failed.

Marks — controlling test execution:

python @pytest.mark.skip(reason="Not implemented yet") def test_future_feature(): ... @pytest.mark.skipif(sys.platform == "win32", reason="Unix only") def test_unix_permissions(): ... @pytest.mark.xfail(reason="Known bug #42") def test_known_broken(): ... # expected to fail — won't count as failure

conftest.py — shared fixtures across files:

Put fixtures in conftest.py in the test directory — pytest loads it automatically and any test file in that directory can use those fixtures.

Useful CLI flags:

bash pytest -v # verbose: show each test name pytest -k "add" # only run tests whose name contains "add" pytest -x # stop after first failure pytest --tb=short # shorter tracebacks pytest tests/test_cart.py # run one file

Examples

Fixture with teardown + parametrize together

Fixtures compose — `cart_with_items` receives `empty_cart` as a parameter; parametrize and fixtures work together naturally

import pytest

class Cart:
    def __init__(self):
        self.items = []
    def add(self, item, price):
        self.items.append({"item": item, "price": price})
    def total(self):
        return sum(i["price"] for i in self.items)

@pytest.fixture
def empty_cart():
    return Cart()

@pytest.fixture
def cart_with_items(empty_cart):
    empty_cart.add("book", 15.0)
    empty_cart.add("pen", 2.5)
    return empty_cart

def test_cart_total(cart_with_items):
    assert cart_with_items.total() == 17.5

@pytest.mark.parametrize("item,price,expected_total", [
    ("notebook", 5.0, 5.0),
    ("laptop", 999.0, 999.0),
])
def test_single_item_total(empty_cart, item, price, expected_total):
    empty_cart.add(item, price)
    assert empty_cart.total() == expected_total

conftest.py for shared DB fixture

session-scoped app fixture creates the DB once; function-scoped db fixture rolls back after each test for isolation

# tests/conftest.py
import pytest
from myapp import create_app, db as _db

@pytest.fixture(scope="session")
def app():
    app = create_app({"TESTING": True, "DATABASE": ":memory:"})
    with app.app_context():
        _db.create_all()
        yield app
        _db.drop_all()

@pytest.fixture
def client(app):
    return app.test_client()

@pytest.fixture
def db(app):
    yield _db
    _db.session.rollback()  # each test gets a clean slate

# tests/test_users.py — just request the fixture by name
def test_get_users_empty(client):
    response = client.get("/users")
    assert response.status_code == 200
    assert response.json == []

How well did you understand this?

Next in Testing

Mocking & Patching

Continue