Python Basics: Checking Documentation Examples with doctest
A short example beside a function can explain how to use it. Python’s standard-library doctest module lets you check that example when the implementation changes.
Before you start
These three independent scripts were tested on Linux with Bash, Python 3.12.14 and Python 3.13.5. They need no extra packages or environment variables. Commands use python3; substitute python3.13 for the other tested interpreter.
Create a fresh working directory and keep this Bash session open:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Save each UTF-8 source block below in this directory. The programs only print results; they do not access the network or create data files. Avoid the filename doctest.py, which would shadow the module.
The >>> lines belong inside the docstrings shown below. Copy the entire Python block into its named file; do not paste these lines into Bash.
Check a small helper’s examples
Save as demo_doctest_1.py:
import doctest
def initials(name):
"""Return uppercase initials from whitespace-separated words.
>>> initials("Ada Lovelace")
'AL'
>>> initials(" grace hopper ")
'GH'
>>> initials(" ")
Traceback (most recent call last):
...
ValueError: name must contain a word
"""
words = name.split()
if not words:
raise ValueError("name must contain a word")
return "".join(word[0].upper() for word in words)
if __name__ == "__main__":
result = doctest.testmod()
print(f"Attempted: {result.attempted}; failed: {result.failed}")
raise SystemExit(1 if result.failed else 0)Run it:
python3 demo_doctest_1.pyOutput:
Attempted: 3; failed: 0testmod() checks docstring examples. Expression results use their interactive representation, including string quotes. The traceback example checks the expected exception; its stack details are ignored. See the doctest documentation.
The tests cover a normal name, repeated whitespace, and an empty input. This helper intentionally uses a simple word-based rule, not a full model of people’s names. The exit code makes an unexpected test failure visible to shell scripts.
Check printed formatting
Save as demo_doctest_2.py:
import doctest
def print_checklist(items):
"""Print a heading, a blank line, and one row of items.
>>> print_checklist(["tea", "bread"])
Shopping
<BLANKLINE>
tea | bread
>>> print_checklist(["tea", "bread"]) # doctest: +NORMALIZE_WHITESPACE
Shopping
<BLANKLINE>
tea | bread
"""
print("Shopping")
print()
print(" | ".join(items))
if __name__ == "__main__":
result = doctest.testmod()
print(f"Attempted: {result.attempted}; failed: {result.failed}")
raise SystemExit(1 if result.failed else 0)Run it:
python3 demo_doctest_2.pyOutput:
Attempted: 2; failed: 0<BLANKLINE> represents an empty output line. NORMALIZE_WHITESPACE treats whitespace sequences as equivalent for that example, including line breaks. Keep exact checks when spacing matters. See option flags.
The first check preserves the checklist’s layout requirement. The second demonstrates a deliberately looser comparison. Neither prints the shopping list to the terminal during a successful test because doctest captures that output.
Catch a deliberate regression
Save as demo_doctest_3.py:
import doctest
def normalize_label(text):
"""Trim surrounding whitespace and convert to lowercase.
>>> normalize_label(" Tea ")
'tea'
"""
return text.lower() # Deliberate bug: missing strip().
if __name__ == "__main__":
try:
doctest.testmod(raise_on_error=True)
except doctest.DocTestFailure as error:
print("Intentional mismatch caught")
print("Expected:", error.example.want.strip())
print("Actual:", error.got.strip())Run it:
python3 demo_doctest_3.pyOutput:
Intentional mismatch caught
Expected: 'tea'
Actual: ' tea 'raise_on_error=True raises a DocTestFailure on an output mismatch. Here we catch it to show stable expected and actual values without machine-specific traceback paths. See the basic API.
The third script deliberately exits normally after catching the failure. This is a teaching demonstration, not a successful regression test. The missing operation is text.strip().lower(). For a real test run, let failures propagate or return a nonzero exit status, as the first two scripts do.
Keep examples deterministic and focused. Avoid printing timestamps, random values, or unordered collections directly. For extensive edge cases and setup, dedicated unit tests can complement these readable examples. Only run doctests from code you trust: the examples execute Python.
Clean up
In the same Bash session, remove only the three scripts and the empty working directory:
rm -- demo_doctest_1.py demo_doctest_2.py demo_doctest_3.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirReferences
Acknowledgements
This post was created with assistance from AI.