Typorithm · medium

Parameterized fixtures and small matrices

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

Step 1 of 4 · intro card

Parameterized fixtures and small matrices

0%

Concept and contract

Vary a reusable dependency while keeping the case count understandable.

A parameterized fixture runs a requesting test with several versions of its setup. This is useful when the same behavior must work against multiple supported configurations. The example supplies a comma and a semicolon as two delimiter configurations for the same tiny serializer.

Distinguish setup variation from data examples. A mark.parametrize table usually describes inputs and expected outputs for a behavior. A parameterized fixture describes a dependency supplied to several tests. Either can be appropriate, but unnecessary combinations multiply the number of collected test items.

Mechanism and worked example

The fixture declares params and returns request.param.

The fixture declares params and returns request.param. Each requesting test receives one delimiter at a time. The worked test checks a two-field serialization against a literal expected value for that delimiter. IDs make failures readable without inspecting a numeric fixture index.

If a test also has three data parameters, two delimiter configurations can produce six items. This Cartesian expansion is deliberate, not free coverage. Keep cases orthogonal and inspect --collect-only before introducing a much larger matrix into CI.

Complete worked example

python

import pytest


def serialize(fields, delimiter):
    return delimiter.join(fields)


@pytest.fixture(params=[",", ";"], ids=["comma", "semicolon"])
def delimiter(request):
    return request.param


def test_two_fields(delimiter):
    expected = {",": "a,b", ";": "a;b"}[delimiter]
    assert serialize(["a", "b"], delimiter) == expected
Reasoning
The fixture declares params and returns request.param. Each requesting test receives one delimiter at a time. The worked test checks a two-field serialization against a literal expected value for that delimiter. IDs make failures readable without inspecting a numeric fixture index. If a test also has three data parameters, two delimiter configurations can produce six items. This Cartesian expansion is deliberate, not free coverage. Keep cases orthogonal and inspect --collect-only before introducing a much larger matrix into CI.
Open in local Python playground ↗

Focused test decision: context is in the complete example

python

@pytest.fixture(params=[",", ";"], ids=["comma", "semicolon"])
def delimiter(request):
    return request.param
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 serializer reverses the fields.

The controlled serializer reverses the fields. Both delimiter configurations reject it. A fake matrix that changes an ID but returns the same configuration each time gives the appearance of variation without new evidence. Assert the actual behavior, not only the parameter label.

Do not combine every unsupported backend with every unrelated input just because parameterization is available. A small targeted compatibility suite is often easier to diagnose. Use indirect parameterization only when the fixture needs to construct a resource from a supplied value.

Controlled bug: the tests must report failure

python

import pytest


def serialize(fields, delimiter):
    return delimiter.join(reversed(fields))


@pytest.fixture(params=[",", ";"], ids=["comma", "semicolon"])
def delimiter(request):
    return request.param


def test_two_fields(delimiter):
    expected = {",": "a,b", ";": "a;b"}[delimiter]
    assert serialize(["a", "b"], delimiter) == expected
Reasoning
The controlled serializer reverses the fields. Both delimiter configurations reject it. A fake matrix that changes an ID but returns the same configuration each time gives the appearance of variation without new evidence. Assert the actual behavior, not only the parameter label. Do not combine every unsupported backend with every unrelated input just because parameterization is available. A small targeted compatibility suite is often easier to diagnose. Use indirect parameterization only when the fixture needs to construct a resource from a supplied value.
Open in local Python playground ↗

Independent practice

Add an empty-fields test that requests the delimiter fixture.

Add an empty-fields test that requests the delimiter fixture. It should produce an empty string for both supported configurations. Retain the two-field test and verify that collection now contains four items.

Type the request.param line and a single test assertion before adding more configurations. Then run a probe that invents a placeholder only for an empty list. Explain which old cases remain valid and which new items expose the defect.

Step 1: Add an empty-fields test for each delimiter fixture parameter. 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


def serialize(fields, delimiter):
    return delimiter.join(fields)


@pytest.fixture(params=[",", ";"], ids=["comma", "semicolon"])
def delimiter(request):
    return request.param


def test_two_fields(delimiter):
    expected = {",": "a,b", ";": "a;b"}[delimiter]
    assert serialize(["a", "b"], delimiter) == expected

# Task: Add an empty-fields test for each delimiter fixture parameter.
Reasoning
Add an empty-fields test for each delimiter fixture parameter.
Open in local Python playground ↗
  • Hint 1: Request delimiter in the new test signature.
  • Hint 2: Expect "" for an empty list with either delimiter.

Solution, tests and trade-offs

The solution adds the empty-input behavior for every fixture parameter.

The solution adds the empty-input behavior for every fixture parameter. Its probe returns a dash for empty input. The two-field cases remain green, while both empty-input items fail with a readable delimiter ID.

An implementation should still validate unsupported delimiters if that is part of its API. This course example defines only two supported configurations and does not claim general CSV escaping. A real CSV writer has quoting, embedded delimiters and line-ending contracts that require separate tests.

Practice solution and acceptance checks

python

import pytest


def serialize(fields, delimiter):
    return delimiter.join(fields)


@pytest.fixture(params=[",", ";"], ids=["comma", "semicolon"])
def delimiter(request):
    return request.param


def test_two_fields(delimiter):
    expected = {",": "a,b", ";": "a;b"}[delimiter]
    assert serialize(["a", "b"], delimiter) == expected

def test_empty_fields(delimiter):
    assert serialize([], delimiter) == ""
Reasoning
The solution adds the empty-input behavior for every fixture parameter. Its probe returns a dash for empty input. The two-field cases remain green, while both empty-input items fail with a readable delimiter ID. An implementation should still validate unsupported delimiters if that is part of its API. This course example defines only two supported configurations and does not claim general CSV escaping. A real CSV writer has quoting, embedded delimiters and line-ending contracts that require separate tests.
Open in local Python playground ↗

Interview reasoning and next step

Explain the difference between direct parameterization, fixture parameters and indirect construction.

Explain the difference between direct parameterization, fixture parameters and indirect construction. Calculate the collected item count for a small matrix and justify each dimension. If collection becomes unexpectedly large, simplify redundant combinations rather than disabling important assertions.

Next examine scope and shared configuration. Reuse should make dependencies easier to understand while preserving fresh mutable data at the point where tests need ownership. Keep matrix construction cheap: creating an expensive resource belongs in fixture setup, not in module-level parameter expressions evaluated during collection.

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-fixture-params 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.

Vary a reusable dependency while keeping the case count understandable.