Typorithm · easy

Errors are part of the API

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

Step 1 of 4 · intro card

Errors are part of the API

0%

Concept and contract

Check the expected exception and keep the protected block narrow.

An invalid input contract should identify an exception type and, when stable and useful, part of its message. A batch size must be positive; zero and negative values raise ValueError with a clear explanation. This is different from returning a sentinel value that callers must remember to inspect.

Test errors separately from successful values. Catch the documented error rather than Exception. A broad catch can mistake an unrelated NameError, import failure or implementation typo for correct rejection of the input. Error tests should make the intended failure more specific, not less.

Mechanism and worked example

pytest.raises acts as a context manager.

pytest.raises acts as a context manager. Put only the operation expected to fail inside its block. If nothing raises, pytest fails the test; if the wrong exception type escapes, the test also fails. The match argument searches the exception message with a regular expression.

The complete example checks a valid batch and the zero case. The message is deliberately short and stable. Literal punctuation with regex meaning requires escaping or re.escape. Do not assert a complete traceback, temporary path or incidental formatting as if it were part of the public API.

Complete worked example

python

import pytest


def batch_size(value):
    if value <= 0:
        raise ValueError("size must be positive")
    return value


def test_valid_size():
    assert batch_size(2) == 2


def test_zero_size_rejected():
    with pytest.raises(ValueError, match="must be positive"):
        batch_size(0)
Reasoning
pytest.raises acts as a context manager. Put only the operation expected to fail inside its block. If nothing raises, pytest fails the test; if the wrong exception type escapes, the test also fails. The match argument searches the exception message with a regular expression. The complete example checks a valid batch and the zero case. The message is deliberately short and stable. Literal punctuation with regex meaning requires escaping or re.escape. Do not assert a complete traceback, temporary path or incidental formatting as if it were part of the public API.
Open in local Python playground ↗

Focused test decision: context is in the complete example

python

with pytest.raises(ValueError, match="must be positive"):
    batch_size(-1)
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 replaces ValueError with TypeError.

The controlled bug replaces ValueError with TypeError. A broad raises(Exception) would accept either and conceal the broken API. The specific expectation rejects the changed type. Placing both setup and the action inside raises can create a similar false positive when setup fails first.

Avoid asserting that every invalid input produces the exact same text when different validation branches have meaningful messages. Conversely, do not depend on implementation-specific exception wording from another library unless your wrapper explicitly owns that wording.

Controlled bug: the tests must report failure

python

import pytest


def batch_size(value):
    if value <= 0:
        raise TypeError("size must be positive")
    return value


def test_valid_size():
    assert batch_size(2) == 2


def test_zero_size_rejected():
    with pytest.raises(ValueError, match="must be positive"):
        batch_size(0)
Reasoning
The controlled bug replaces ValueError with TypeError. A broad raises(Exception) would accept either and conceal the broken API. The specific expectation rejects the changed type. Placing both setup and the action inside raises can create a similar false positive when setup fails first. Avoid asserting that every invalid input produces the exact same text when different validation branches have meaningful messages. Conversely, do not depend on implementation-specific exception wording from another library unless your wrapper explicitly owns that wording.
Open in local Python playground ↗

Independent practice

First run the valid and zero cases.

First run the valid and zero cases. For practice, add a negative input case with the same documented error type and message fragment. Keep the protected block to a single call. Predict how a function that rejects only zero would behave for negative one.

Type the short context-manager test before copying the full example. The aim is to learn the arrangement and contract, not to memorise an entire suite. If you add validation for strings later, decide whether that is TypeError or ValueError before writing its expected result.

Step 1: Add a negative-size rejection test with a narrow raises block. 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 batch_size(value):
    if value <= 0:
        raise ValueError("size must be positive")
    return value


def test_valid_size():
    assert batch_size(2) == 2


def test_zero_size_rejected():
    with pytest.raises(ValueError, match="must be positive"):
        batch_size(0)

# Task: Add a negative-size rejection test with a narrow raises block.
Reasoning
Add a negative-size rejection test with a narrow raises block.
Open in local Python playground ↗
  • Hint 1: Use the documented ValueError, not the base Exception type.
  • Hint 2: Call batch_size(-1) inside the context manager.

Solution, tests and trade-offs

The solution includes a negative-value regression.

The solution includes a negative-value regression. The practice probe changes <= 0 into == 0: valid two and invalid zero still behave as before, but negative one now passes through. The new raises test detects that specific missing partition.

An error test that passes is evidence of the documented rejection for that input. It does not establish that every state was rolled back or that no output was written first. If such effects matter, assert them after leaving the raises block with separately controlled resources.

Practice solution and acceptance checks

python

import pytest


def batch_size(value):
    if value <= 0:
        raise ValueError("size must be positive")
    return value


def test_valid_size():
    assert batch_size(2) == 2


def test_zero_size_rejected():
    with pytest.raises(ValueError, match="must be positive"):
        batch_size(0)

def test_negative_size_rejected():
    with pytest.raises(ValueError, match="must be positive"):
        batch_size(-1)
Reasoning
The solution includes a negative-value regression. The practice probe changes <= 0 into == 0: valid two and invalid zero still behave as before, but negative one now passes through. The new raises test detects that specific missing partition. An error test that passes is evidence of the documented rejection for that input. It does not establish that every state was rolled back or that no output was written first. If such effects matter, assert them after leaving the raises block with separately controlled resources.
Open in local Python playground ↗

Interview reasoning and next step

Explain why a narrow raises block and a specific exception type protect different failure modes.

Explain why a narrow raises block and a specific exception type protect different failure modes. State that match is a regex search, not exact string equality. If exact message matching is appropriate, use an anchored escaped pattern or inspect the captured exception explicitly.

Next, collect related input/error cases with parameterization while retaining readable names. Keep invalid inputs purposeful: a handful of boundary partitions is more informative than a large random sample with no independent oracle.

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

Check the expected exception and keep the protected block narrow.