Chapter 5

Exceptions & Testing

5.0 Intro · 5.1 Exceptions · 5.2 Unit Testing

← → or Space to navigate · F for fullscreen

Why Exceptions and Testing?

Handle failures gracefully and verify that code does what it claims

Two Kinds of Problems

Runtime failures

The program stops because Python raises an exception.

age = int("twenty")
# ValueError

Exception handling lets the program recover or report a useful message.

Semantic bugs

The program runs, but the answer is wrong.

def average(nums):
    return sum(nums) / (len(nums) - 1)
# wrong formula

Tests catch wrong behavior before users do.

Chapter 5 Toolkit

Tracebacks Follow the error path to the failing line.
try / except Recover from expected runtime failures.
raise Signal invalid input or state clearly.
logging Record what happened without cluttering output.
assert Check assumptions while developing.
tests Make behavior repeatable and verifiable.

5.1 Exceptions

Tracebacks, try/except, else/finally, custom exceptions, and logging

Reading Tracebacks

  • Start at the bottom for the exception type and message.
  • Look upward to find the line in your code that failed.
  • Tracebacks show the call stack: who called whom.

A traceback is a map, not just an error message.

def parse_age(text):
    return int(text)

def register(raw_age):
    age = parse_age(raw_age)
    return {"age": age}

register("twenty")
# ValueError: invalid literal for int()

try / except

  • Put risky code in the try block.
  • Catch specific exception types.
  • Return, retry, or explain the failure.

Avoid bare except:. It can hide real bugs.

def parse_int(text):
    try:
        return int(text)
    except ValueError:
        print(f"{text!r} is not an integer")
        return None

print(parse_int("42"))     # 42
print(parse_int("hello"))  # None

Multiple Exceptions

Different failures deserve different responses.

Exception Common cause
ValueError Invalid value, like int("x")
FileNotFoundError Missing file path
KeyError Missing dictionary key
ZeroDivisionError Division by zero
def get_score(scores, name):
    try:
        return scores[name]
    except KeyError:
        return "missing student"
    except TypeError:
        return "scores must be a dict"

else and finally

  • else runs only if no exception occurred.
  • finally always runs — exception or not.
  • Use finally for cleanup.
try:
    value = int("42")
except ValueError:
    print("bad input")
else:
    print("converted:", value)
finally:
    print("done")

Raising Exceptions

  • Use raise when a function cannot honor its contract.
  • Choose a specific built-in exception when possible.
  • Create custom exceptions for domain-specific failures.
class OverdraftError(Exception):
    pass

def withdraw(balance, amount):
    if amount < 0:
        raise ValueError("amount must be positive")
    if amount > balance:
        raise OverdraftError("insufficient funds")
    return balance - amount

Logging

Logging records diagnostic information without scattered print() calls.

Level Use
DEBUG Detailed developer info
INFO Normal progress
WARNING Unexpected but recoverable
ERROR Operation failed
import logging

logging.basicConfig(level=logging.INFO)

def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        logging.error("division by zero")
        return None

5.2 Unit Testing

pytest, unittest, doctest, fixtures, mocking, parametrization, and coverage

What is a Unit Test?

  • A small automated check for one behavior.
  • Tests one function, method, or class behavior.
  • Should be repeatable and independent.

Tests turn examples into executable evidence.

def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

pytest

  • Test files start with test_.
  • Test functions start with test_.
  • Use plain assert statements.

Run from the terminal with python -m pytest.

# test_calc.py
from calc import add, divide

def test_add():
    assert add(2, 3) == 5

def test_divide():
    assert divide(10, 2) == 5

Testing Exceptions

Good tests check both normal behavior and expected failure behavior.

  • Use pytest.raises for expected exceptions.
  • Test the contract, not implementation details.
import pytest

def divide(a, b):
    if b == 0:
        raise ZeroDivisionError("b cannot be 0")
    return a / b

def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)

unittest

  • Python's built-in testing framework.
  • Organizes tests inside classes.
  • Uses assertion methods like assertEqual.

You will see unittest in many older or standard-library-oriented projects.

import unittest

class TestCalc(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)

if __name__ == "__main__":
    unittest.main()

Doctests

  • Examples embedded in docstrings.
  • Useful for simple functions and documentation.
  • Best when outputs are short and stable.
def square(x):
    """Return x squared.

    >>> square(4)
    16
    >>> square(-3)
    9
    """
    return x * x

Advanced pytest Tools

Tool Use
Parametrize Run one test with many inputs
Fixture Reusable setup data or objects
Mock Replace slow or external dependencies
Coverage Find code paths tests did not run
import pytest

@pytest.mark.parametrize(
    "a,b,expected",
    [(2, 3, 5), (0, 0, 0), (-1, 1, 0)]
)
def test_add_cases(a, b, expected):
    assert add(a, b) == expected

Testing Strategy

Test these first

  • Normal cases
  • Boundary cases
  • Error cases
  • Previously fixed bugs

Keep tests useful

  • Small and focused
  • Readable names
  • Independent of test order
  • Fast enough to run often

Testing is design feedback: if code is hard to test, it is often doing too much.

Chapter 5 — Quick Reference

Concept Key syntax / notes
Catch exception try: / except ValueError:
Exception object except Exception as e:
Cleanup finally always runs
Raise exception raise ValueError("message")
Logging logging.info(), logging.error()
pytest test def test_name(): assert result == expected
Expected exception with pytest.raises(Error):
unittest class TestX(unittest.TestCase):
doctest Examples in docstrings with >>>

End of Chapter 5

Next: Chapter 6 — Lists

tracebacks · exceptions · logging · pytest · unittest · doctest