Typorithm · easy

Parameter cases with readable identities

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

Step 1 of 4 · intro card

Parameter cases with readable identities

0%

Concept and contract

Replace repetition with a small table of independent examples.

A parameter table is useful when the same behavior should be checked for several intentional input partitions. Whitespace normalization can be tested with a padded name and a name that already needs no trimming. Each row pairs a concrete input with an independently chosen expected value.

The table is not an invitation to generate hundreds of arbitrary cases. Prefer a few rows that would expose different mistakes. A readable ID turns a failure into a meaningful case name rather than a numeric position that must be looked up elsewhere.

Mechanism and worked example

pytest.mark.parametrize supplies arguments by the names in its first string.

pytest.mark.parametrize supplies arguments by the names in its first string. The test function receives the corresponding row values. The worked example runs the same assertion twice, and IDs identify padded and already-clean inputs. Each parameter row is a separate collected test item.

The expected column stays literal: it is not computed by calling strip again. This separates the specification from the implementation. Keep the number and order of parameter names aligned with each row; a collection error is different from a failed test body.

Complete worked example

python

import pytest


def clean_name(value):
    return value.strip()


@pytest.mark.parametrize("raw, expected", [
    pytest.param(" Ada ", "Ada", id="padded"),
    pytest.param("Ada", "Ada", id="already-clean"),
])
def test_clean_name(raw, expected):
    assert clean_name(raw) == expected
Reasoning
pytest.mark.parametrize supplies arguments by the names in its first string. The test function receives the corresponding row values. The worked example runs the same assertion twice, and IDs identify padded and already-clean inputs. Each parameter row is a separate collected test item. The expected column stays literal: it is not computed by calling strip again. This separates the specification from the implementation. Keep the number and order of parameter names aligned with each row; a collection error is different from a failed test body.
Open in local Python playground ↗

Focused test decision: context is in the complete example

python

@pytest.mark.parametrize("raw, expected", [(" Ada ", "Ada")])
def test_clean_name(raw, expected):
    assert clean_name(raw) == expected
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 bug uses rstrip, which leaves leading spaces untouched.

The controlled bug uses rstrip, which leaves leading spaces untouched. The padded case fails while the already-clean case passes. A loop inside one test can obscure later cases after the first assertion fails; parameterization lets pytest report each collected row.

Mutable parameter values are passed as objects, not magically cloned per case. If the code mutates a supplied list or dictionary, build fresh state deliberately or copy at the correct boundary. Parameterization is for cases, while fixtures are for reusable setup and lifetime.

Controlled bug: the tests must report failure

python

import pytest


def clean_name(value):
    return value.rstrip()


@pytest.mark.parametrize("raw, expected", [
    pytest.param(" Ada ", "Ada", id="padded"),
    pytest.param("Ada", "Ada", id="already-clean"),
])
def test_clean_name(raw, expected):
    assert clean_name(raw) == expected
Reasoning
The controlled bug uses rstrip, which leaves leading spaces untouched. The padded case fails while the already-clean case passes. A loop inside one test can obscure later cases after the first assertion fails; parameterization lets pytest report each collected row. Mutable parameter values are passed as objects, not magically cloned per case. If the code mutates a supplied list or dictionary, build fresh state deliberately or copy at the correct boundary. Parameterization is for cases, while fixtures are for reusable setup and lifetime.
Open in local Python playground ↗

Independent practice

Add the empty-string row with a descriptive ID.

Add the empty-string row with a descriptive ID. This is a small exercise in extending a table, not in rewriting the normalizer. Retain both existing rows and check that collection grows from two to three items.

If you later add all-whitespace input, decide whether empty output is accepted or rejected. Do not silently introduce a validation requirement while calling it normalization. State the policy before adding another row.

Step 1: Add an empty-string case and keep its expected value empty. 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 clean_name(value):
    return value.strip()


@pytest.mark.parametrize("raw, expected", [
    pytest.param(" Ada ", "Ada", id="padded"),
    pytest.param("Ada", "Ada", id="already-clean"),
])
def test_clean_name(raw, expected):
    assert clean_name(raw) == expected

# Task: Add an empty-string case and keep its expected value empty.
Reasoning
Add an empty-string case and keep its expected value empty.
Open in local Python playground ↗
  • Hint 1: An empty input is a separate partition from padded text.
  • Hint 2: Use an empty-case ID if you extend the table, or a separate named test.

Solution, tests and trade-offs

The solution adds an empty case to the parameter table.

The solution adds an empty case to the parameter table. The acceptance probe substitutes a fallback name only for empty input. Both initial examples remain correct, but the added row rejects the invented fallback. This is an observable behavior check with a specific independently calculated expectation.

A test item ID should aid diagnosis; it should not encode secrets or user data. Here every string is synthetic and small. If there are many cases, group them around separate contracts instead of creating a single table that mixes unrelated validation rules.

Practice solution and acceptance checks

python

import pytest


def clean_name(value):
    return value.strip()


@pytest.mark.parametrize("raw, expected", [
    pytest.param(" Ada ", "Ada", id="padded"),
    pytest.param("Ada", "Ada", id="already-clean"),
])
def test_clean_name(raw, expected):
    assert clean_name(raw) == expected

def test_empty_name_remains_empty():
    assert clean_name("") == ""
Reasoning
The solution adds an empty case to the parameter table. The acceptance probe substitutes a fallback name only for empty input. Both initial examples remain correct, but the added row rejects the invented fallback. This is an observable behavior check with a specific independently calculated expectation. A test item ID should aid diagnosis; it should not encode secrets or user data. Here every string is synthetic and small. If there are many cases, group them around separate contracts instead of creating a single table that mixes unrelated validation rules.
Open in local Python playground ↗

Interview reasoning and next step

Explain how parameterization changes collection and reporting without changing the asserted contract.

Explain how parameterization changes collection and reporting without changing the asserted contract. Show why expected values should not simply repeat the transformation. Discuss when separate tests, a table or a fixture best communicates the behavior.

The next lessons introduce approximate numerical comparisons and fresh fixtures. Keep typing practice short: a decorator and a clear test function are enough to learn this mechanism, while the complete runnable module provides context.

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-parametrize 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.

Replace repetition with a small table of independent examples.