Typorithm · easy

Fresh fixtures and visible dependencies

pythonpytesttestingindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Fresh fixtures and visible dependencies

0%

Concept and contract

Separate reusable setup from the behavior each test asserts.

A fixture supplies setup by a declared dependency name. If a test requests basket, pytest locates that fixture, executes it at the configured scope and passes its result to the test. The default function scope creates a fresh value for each test invocation.

Freshness matters when the test mutates its input. Two tests should not depend on which ran first. Reusing a module-level list or a broad-scoped mutable fixture can turn a small suite into an order-dependent system with hidden state.

Mechanism and worked example

The worked fixture returns a new list containing one item.

The worked fixture returns a new list containing one item. One test observes its initial contents; another appends and checks the resulting contents. Because the fixture is function-scoped, both start with the same independent setup, not with the other test’s modified list.

Fixture dependencies can be explicit arguments of other fixtures too. Keep the graph understandable: do not create a long chain of fixtures that only rename the same constant. A simple literal in one test may be clearer than a fixture used once with no meaningful setup responsibility.

Complete worked example

python

import pytest


@pytest.fixture
def basket():
    return ["apple"]


def test_initial_item(basket):
    assert basket == ["apple"]


def test_append_item(basket):
    basket.append("pear")
    assert basket == ["apple", "pear"]
Reasoning
The worked fixture returns a new list containing one item. One test observes its initial contents; another appends and checks the resulting contents. Because the fixture is function-scoped, both start with the same independent setup, not with the other test’s modified list. Fixture dependencies can be explicit arguments of other fixtures too. Keep the graph understandable: do not create a long chain of fixtures that only rename the same constant. A simple literal in one test may be clearer than a fixture used once with no meaningful setup responsibility.
Open in local Python playground ↗

Focused test decision: context is in the complete example

python

@pytest.fixture
def basket():
    return ["apple"]
Focused test decision
Type this short test decision after understanding the complete runnable module. Desktop typing records practice; Run tests in the local playground executes real pytest.

Counterexamples and common mistakes

The controlled fixture returns the wrong initial item, so the assertions reject the setup error.

The controlled fixture returns the wrong initial item, so the assertions reject the setup error. Another tempting shortcut is to return a shared mutable global list. That can pass an isolated test yet fail when another test leaves a mutation behind.

autouse fixtures execute without a visible test argument. They can be appropriate for a narrow environmental invariant, but they also hide dependencies. Prefer explicit fixture arguments while learning, and document any suite-wide behavior that would otherwise surprise a reader.

Controlled bug: the tests must report failure

python

import pytest


@pytest.fixture
def basket():
    return ["pear"]


def test_initial_item(basket):
    assert basket == ["apple"]


def test_append_item(basket):
    basket.append("pear")
    assert basket == ["apple", "pear"]
Reasoning
The controlled fixture returns the wrong initial item, so the assertions reject the setup error. Another tempting shortcut is to return a shared mutable global list. That can pass an isolated test yet fail when another test leaves a mutation behind. autouse fixtures execute without a visible test argument. They can be appropriate for a narrow environmental invariant, but they also hide dependencies. Prefer explicit fixture arguments while learning, and document any suite-wide behavior that would otherwise surprise a reader.
Open in local Python playground ↗

Independent practice

Run the tests in their original and reversed order in a local terminal.

Run the tests in their original and reversed order in a local terminal. In the browser, each complete run starts a fresh interpreter, while fixtures still control state between tests within that run. Add a test that clears its own list and verifies the empty result.

Retain the initial-state test. If a shared fixture leaks mutations, it will now conflict with the mutating tests. Do not repair such failures by sorting test names or forcing an execution order; fix ownership and fixture scope.

Step 1: Add a test that clears its own fixture value and asserts the empty result. Step 2: Keep the existing passing cases. Add the smallest new assertion first; predict the failure before running pytest.

Practice starter: solve before reading the solution

python

import pytest


@pytest.fixture
def basket():
    return ["apple"]


def test_initial_item(basket):
    assert basket == ["apple"]


def test_append_item(basket):
    basket.append("pear")
    assert basket == ["apple", "pear"]

# Task: Add a test that clears its own fixture value and asserts the empty result.
Reasoning
Add a test that clears its own fixture value and asserts the empty result.
Open in local Python playground ↗
  • Hint 1: Request basket explicitly as the test argument.
  • Hint 2: Assert the result after clear, not only that the method returned.

Solution, tests and trade-offs

The solution adds a clear operation and its observable result.

The solution adds a clear operation and its observable result. The practice probe returns a small list subclass whose clear method does nothing. The original initial-state and append tests still pass, but the new test detects the missing operation.

This probe checks the added assertion’s sensitivity, not a claim that custom list subclasses are a likely production issue. In a real application replace it with the relevant repository or collection abstraction. The general lesson is to test the promised effect rather than merely calling a method.

Practice solution and acceptance checks

python

import pytest


@pytest.fixture
def basket():
    return ["apple"]


def test_initial_item(basket):
    assert basket == ["apple"]


def test_append_item(basket):
    basket.append("pear")
    assert basket == ["apple", "pear"]

def test_clear_owned_basket(basket):
    basket.clear()
    assert basket == []
Reasoning
The solution adds a clear operation and its observable result. The practice probe returns a small list subclass whose clear method does nothing. The original initial-state and append tests still pass, but the new test detects the missing operation. This probe checks the added assertion’s sensitivity, not a claim that custom list subclasses are a likely production issue. In a real application replace it with the relevant repository or collection abstraction. The general lesson is to test the promised effect rather than merely calling a method.
Open in local Python playground ↗

Interview reasoning and next step

Explain why function scope provides a new fixture value to each test and why a fixture requested twice in one test is cached within that test’s context.

Explain why function scope provides a new fixture value to each test and why a fixture requested twice in one test is cached within that test’s context. Distinguish this from a deep copy of every nested input: pytest does not automatically clone arbitrary shared values.

Next examine yield cleanup and wider scope. Test resource lifetime is part of the design; a fast suite that leaks state can be less useful than a slightly slower suite with explicit independent setup.

Optional stretch — after the core task works: Introduce one deliberate implementation mistake and explain which assertion catches it. A green run alone is not evidence that every possible bug is covered.

Computer lab: https://applydjinn.com/typorithm/pytest-lab-20261106.zip. Run verify.py in an isolated environment. Worked examples and solutions pass, controlled counterexamples fail, and the new practice assertions reject a mutation missed by the baseline. Run --practice pyq-fixtures after changing the supplied exercise. Python executes on your computer or in the browser, without submitting code to a server. Native question progress is a separate learning record.

Separate reusable setup from the behavior each test asserts.