pytest: Fixtures & Parametrize
Reusable fixtures, parametrized tests, marks, and effective test organization
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_totalconftest.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