Interview preparation guide

pytest Interview Questions: Fixture Scope, Parametrization and Patch Cleanup

Prepare for pytest interviews by explaining what a test proves and why it might fail only when another test runs first. This original exercise uses a small label-formatting function, two mutable-fixture tests and controlled environment changes. It needs no application server, network requests or database.

What makes a fixture different from a helper function?

A pytest fixture supplies a dependency requested by a test or another fixture. Its scope controls how long its result is shared. A regular helper is called directly where you need it. Choose a fixture when lifecycle or dependency reuse is part of the setup, not merely because a function appears in a test file.

For this exercise, the bucket fixture returns a fresh list for each test. The label_for function is the small unit under test. Before running the file, predict which tests would be affected if the list were shared across the entire module.

Run the complete example

Save the code as test_labels.py in a scratch directory. Install pytest in an isolated Python environment, then run python -m pytest -q test_labels.py. The exact file was checked with Python 3.12.14 and pytest 9.1.1: eight tests passed. The inputs and service are fictional teaching material.

import os
import pytest


def label_for(name):
    if not isinstance(name, str) or not name.strip():
        raise ValueError('name must be non-empty text')
    prefix = os.environ.get('LABEL_PREFIX', 'item')
    return f'{prefix}:{name.strip()}'


@pytest.fixture
def bucket():
    return []


def test_first_user(bucket):
    bucket.append('first')
    assert bucket == ['first']


def test_second_user(bucket):
    bucket.append('second')
    assert bucket == ['second']


@pytest.mark.parametrize('raw, expected', [
    ('  parcel  ', 'item:parcel'),
    ('a:b', 'item:a:b'),
])
def test_valid_names(monkeypatch, raw, expected):
    monkeypatch.delenv('LABEL_PREFIX', raising=False)
    assert label_for(raw) == expected


@pytest.mark.parametrize('raw', ['', '   ', None])
def test_invalid_names(raw):
    with pytest.raises(ValueError, match='non-empty text'):
        label_for(raw)


def test_patch_cleanup(monkeypatch):
    monkeypatch.setenv('LABEL_PREFIX', 'outer')
    with monkeypatch.context() as patch:
        patch.setenv('LABEL_PREFIX', 'inner')
        assert label_for('box') == 'inner:box'
    assert label_for('box') == 'outer:box'

Why does changing scope create an order-dependent failure?

Change only the bucket decorator to @pytest.fixture(scope="module") and run the whole file again. In the observed run, test_first_user passed and test_second_user failed because the second test received ["first", "second"] instead of ["second"]. The other seven tests passed. The failure demonstrates shared mutable state, not a defect in list.append.

Run the second test alone and the shared list starts empty, which can conceal the problem. Restore the default function scope to make each test independent. Using a wider scope may be appropriate for an expensive read-only resource, but it does not make mutable state safe automatically.

Interview follow-up

Question: “Could you fix this by changing the expected value to include first?”

Answer: “That would encode dependency on another test. I would isolate the fixture or reset the relevant state through a reliable lifecycle, then verify each test independently and together.”

Question: “Would renaming the tests solve it?”

Answer: “No. A different order may change which test fails, but it does not remove the shared state.”

What does parametrization add here?

The two valid inputs and three invalid inputs become separate test cases with their own reports. That makes it clear whether trimming, ordinary text or rejection failed. It is more informative than one long loop that stops at its first failing assertion.

The invalid cases use an expected exception and message fragment. They do not merely assert that some failure happened. The valid cases explicitly remove LABEL_PREFIX before calling the function, so a developer’s shell setting cannot silently change the expected result.

Add a boundary with a reason

Try the integer 7 as another invalid input. Explain why it should be rejected under the function’s stated text-only contract.

Try the string "0" as a valid input. It is non-empty text, so rejecting it would confuse string content with a numeric false value.

Do not add dozens of equivalent names just to increase the test count; choose cases that test a different behavior.

How do you prove a monkeypatch was undone?

The final test sets an outer environment value, creates a narrower monkeypatch context and changes the value inside it. After the context exits, the assertion checks that the outer value is restored. The enclosing fixture then restores the environment when the test ends.

This tests cleanup inside one test rather than relying on a later test to run in a particular order. When patching a function, also check the name used by the code under test: patching a different import location can leave the actual dependency untouched.

Explain the boundary

This file tests formatting and selected environment behavior. It does not prove a real label printer, queue or external service works.

If a later implementation sends requests, keep these focused tests and add a separately scoped integration check for the actual dependency rather than pretending a mock verifies the service.

A compact oral walkthrough

Practice explaining the exercise in this order: contract, setup, action, observable assertion, cleanup and limitation. Then explain the deliberately failing scope change without changing the expected business behavior. That demonstrates diagnosis rather than memorized pytest vocabulary.

Questions, answered

Should fixtures always use function scope?

No. Choose scope based on lifecycle and safe sharing. This mutable-list example needs independent state; an expensive read-only resource may justify a broader scope.

Does parametrization run tests in parallel?

Parametrization creates cases. It does not by itself introduce parallel execution.

Is monkeypatch a substitute for integration tests?

No. It controls selected dependencies and environment behavior for a focused test. A real external interaction needs its own appropriate verification.

Sources and examples

ResumizeAI team. Examples are fictional teaching material, not customer results. Replace sample details with your own facts.

Your experience. A complete resume.

Build in Resumize with ATS-friendly templates, guided editing and a live preview. Start free, then upgrade when you need more.

Free account required. Free-plan PDF exports include a watermark.