AlgoMaster Logo

doctest

Low Priority25 min readUpdated September 21, 2026
Listen to this chapter
Unlock Audio

doctest is Python's built-in tool for turning the examples inside docstrings into actual tests. A function gets a few interactive REPL sessions pasted into its docstring to show how it works, and doctest runs them as tests on demand. It's the only framework where documentation and verification are literally the same characters.

What doctest Is

A doctest is a chunk of text in a docstring that looks like a copy of an interactive Python session. Lines starting with >>> are the code; the lines after them are the expected output. When doctest runs the docstring, it executes each >>> line, captures whatever the line prints (or returns, for an expression), and compares it character-by-character to the text that follows.

The smallest useful example:

Save that to pricing.py. Then run:

That's it. No imports, no extra test files, no framework setup. The docstring is the test. Strip the -v flag and doctest only prints when something fails, which is the usual mode.

The shape that makes this work is rigid. >>> (three greater-than signs followed by a single space) marks an input line. A blank line ends the example. Continuation lines (inside multi-line statements) start with .... Anything between the input line and the next blank line, or the next >>>, is treated as expected output.

Three examples in one docstring. The third one assigns a result before checking it. doctest keeps a fresh namespace per docstring, so the assignment in the third example doesn't leak into other docstrings.

Why This Pattern Exists

Most testing frameworks separate tests from code. The function lives in one file, the test lives in tests/test_thing.py, and the two stay synchronized through discipline. doctest does the opposite: the test sits inside the function it tests, in the form anyone reading the help text will see.

That collocation gives doctest two real strengths.

Documentation that can't go stale. Comments can lie. Docstring examples can lie too, but only until doctest runs. The moment a refactor changes the return value, the docstring example becomes a failing test, and the build complains. Users who copy-paste an example from the docs get code that actually works, because every example was executed as recently as the last test run.

Examples that match how Python feels to use. Most real Python learning happens in the REPL: type a thing, see what it returns, adjust, repeat. A doctest is a transcript of that experience. There's no boilerplate, no assertEqual, no framework vocabulary in between the reader and the behavior.

The pattern has limits. For the use case it fits (library docs, tutorial code, small utility functions where the inputs and outputs are short enough to read in passing), nothing else gives "running examples in the docstring" without building a custom framework around it.

How doctest Parses and Runs Examples

The model is simple enough that knowing it removes most of the surprises. When doctest runs on a module, it does this for every docstring it finds:

  1. Scan the docstring line by line, looking for >>> and ... markers.
  2. Group consecutive input lines plus their expected output into one example.
  3. For each example, compile the input lines as a code block.
  4. Execute the block in a private namespace (one per docstring).
  5. Capture stdout and the value of the last expression.
  6. Compare the captured text to the expected output, exactly.

The diagram below shows the flow.

One pitfall in the match check: doctest doesn't run a smart comparison. It compares strings. If a function returns 0.1 + 0.2, the captured output is the literal 0.30000000000000004, not 0.3. The docstring must say 0.30000000000000004 too, or the test fails.

A failing example looks like this:

Running python -m doctest pricing.py prints:

The output shows the file, the line number, the input that ran, the expected output, and the actual output. Enough to fix it. The first example (shipping_fee(60)) still passed; doctest doesn't stop at the first failure inside a single docstring.

The namespace rule deserves more explanation. Each docstring runs in its own fresh namespace. Names defined in one function's docstring don't leak into another's. Inside a single docstring, examples share the namespace, so this works:

The first example creates cart. The second example uses it. The third inspects it. All three see the same local namespace because they're in the same docstring.

Each docstring gets its own namespace, but every example within a docstring shares state. If example three depends on example one having run, doctest preserves that. To isolate examples, put them in separate docstrings, or rebuild state at the top of each example block.

Running doctest

Two main ways to run doctests cover slightly different situations.

Command Line: python -m doctest

Pass one or more files to python -m doctest. It loads each module, walks the docstrings, and runs everything it finds.

Silent on success. Run with -v (verbose) to see every example pass or fail:

Multiple files can be passed at once:

This form fits CI pipelines or a Makefile target. It's also the simplest way to check a single file during development.

Inside a Script: doctest.testmod()

For a file that tests itself when run directly (common in tutorial-style scripts), add this at the bottom:

Now python pricing.py runs the doctests automatically. testmod() returns a TestResults named tuple with attempted and failed counts, useful for making the script exit non-zero on failure:

testmod() accepts a verbose=True keyword, equivalent to passing -v on the command line. doctest.testfile("some.txt") runs doctests stored in plain text files instead of docstrings, useful for longer tutorials or README files where prose drives the structure.

Strengths: Where doctest Fits

doctest fits well in three specific situations.

Library API documentation. For a library where users will read the help text or the rendered docs, every function-level docstring example becomes proof that the example works. Tools like Sphinx render doctests as code blocks in HTML, so the user sees the same example that was tested. The Python standard library uses doctest heavily for this reason: decimal, pathlib, and textwrap all have docstrings full of >>> blocks.

Tutorials and teaching materials. A .txt file passed to doctest.testfile can contain prose, code blocks, expected outputs, and explanatory text, all readable as a tutorial and runnable as tests. If a code block stops matching, the tutorial broke. The Python "What's New" pages and the standard library's how-to documents use this pattern.

Quick smoke tests for small functions. For a five-line utility function where the inputs and outputs are short, doctest is the lowest-ceremony way to lock in behavior. No new file, no import, no class setup, no framework syntax. The REPL session goes straight into the docstring.

Three lines of test for three lines of code, in the same place the user reads the help. That's the sweet spot.

Weaknesses: Where doctest Falls Apart

The same things that make doctest pleasant for small examples make it painful for anything bigger.

Whitespace sensitivity. A trailing space in the expected output, a missing newline, an extra tab anywhere, and the test fails with a comparison that visually looks identical to the actual output. Editors that strip trailing whitespace can fix examples that were broken without anyone noticing, and editors that don't can break examples that were working.

Sensitivity to `repr` changes. When a function returns a custom object, the expected output is the repr() of that object. Change the __repr__ method and every doctest that mentions the object breaks. The same applies to the formatting of dict and set displays, which can vary across versions.

Dict ordering. Before Python 3.7, dict iteration order was implementation-defined. Code like >>> {"a": 1, "b": 2} could print either {'a': 1, 'b': 2} or {'b': 2, 'a': 1}, and a doctest would pass on one Python version and fail on another. Python 3.7+ guarantees insertion order, which fixes most of the problem, but doctests that build dicts dynamically (from set operations, from iteration over a set) can still be non-deterministic.

Float precision. >>> 0.1 + 0.2 prints 0.30000000000000004. Any doctest involving floating-point arithmetic has to either show the full value or use the ELLIPSIS directive.

No setup or teardown. Each docstring runs in a fresh namespace, but there's no equivalent of pytest fixtures. A doctest that needs a temporary database, a fake HTTP service, or a fresh file system has to build it inline in the docstring, which clutters the documentation.

Hard to organize at scale. A test suite of fifty test_* functions can be filtered, marked, parametrized, and grouped. A test suite of fifty docstring examples is fifty docstrings, scattered across however many functions exist. Doctests don't scale into a real test suite. They scale into documentation that's verified.

The summary: doctest is great when the test is also the example. It's bad when the test is just a test.

Directives: ELLIPSIS, NORMALIZE_WHITESPACE, IGNORE_EXCEPTION_DETAIL

Three directives handle most of the brittleness problems. Enable them by adding # doctest: +DIRECTIVE_NAME after the input line.

ELLIPSIS

ELLIPSIS allows ... in the expected output to match any text. Useful for any output that contains values that shouldn't be pinned down: memory addresses, timestamps, long sequences where only the start or end matters.

The ... in the expected output matches the actual id value, whatever it happens to be. Without ELLIPSIS, the test would need to know the exact id ahead of time, which is impossible for values that change between runs.

ELLIPSIS is also a clean approach for floating-point values without pinning the precision:

0.300000... matches 0.30000000000000004, 0.30000000123, or any other value that starts with 0.300000. Loose enough to absorb float drift, tight enough to catch real errors.

NORMALIZE_WHITESPACE

NORMALIZE_WHITESPACE collapses any run of whitespace (spaces, tabs, newlines) into a single space for comparison purposes. It applies when the output has extra spacing that doesn't matter.

The expected output in the docstring can use any indentation that's convenient; doctest treats consecutive whitespace as a single space when matching. The flag is especially useful when wrapping long expected lines, where the wrap point in the docstring shouldn't have to match the wrap point in the output.

IGNORE_EXCEPTION_DETAIL

When a doctest expects an exception, the default behavior is to match the exception class and the message. Exception messages change often (different Python versions, different platforms, different locale settings), so this is brittle.

IGNORE_EXCEPTION_DETAIL makes doctest check only the exception class:

The actual error message says discount must be 0-100, got -5, but IGNORE_EXCEPTION_DETAIL skips the message comparison. As long as ValueError is raised, the test passes. The ... after Traceback is part of the exception syntax in doctest (not the ELLIPSIS directive); it signals that the traceback frames are not part of the comparison.

Enabling Directives Globally

To enable a directive for every example in a module, pass it on the command line:

Or set it as the module default by passing optionflags to testmod:

The flags are bitwise OR'd together. Once enabled at the module level, every example uses them without individual # doctest: +ELLIPSIS comments.

doctest vs unittest vs pytest

The three frameworks solve overlapping problems and each one fits a different situation. A side-by-side comparison:

Concerndoctestunittestpytest
Where the test livesIn the docstringIn a test_*.py fileIn a test_*.py file
Test syntaxCopy-pasted REPL sessionMethods on TestCase subclassFunctions starting with test_
Setup and teardownNone built-insetUp / tearDown methodsFixtures
ParametrizationNoneSubtests or loops@pytest.mark.parametrize
Output comparisonExact string matchMethod-based (assertEqual etc.)Plain assert with rewriting
Best forDocumentation examplesOO-style structured testsGeneral-purpose testing
Worst forAnything beyond small examplesHeavy boilerplate per testNothing meaningful
In the stdlibYesYesNo (third-party)

A rough rule: write doctests for examples readers will encounter in the docs. Write unittest or pytest tests for behavior that needs serious checking, like edge cases, error paths, fixtures, mocks, and parametrization. The two co-exist comfortably in the same project.

Integrating doctest with pytest

doctest doesn't have to run separately. pytest has a --doctest-modules flag that collects every docstring in every importable module and runs the examples as tests, alongside regular pytest tests.

That single command runs:

  • All test_* functions and Test* classes (the usual pytest discovery)
  • Every doctest in every docstring in every module in the project

The doctests show up in the pytest report with descriptive names, fail with the same kind of structured output, and respect the same -v, -x, and --tb flags as regular tests.

There's a related flag for plain-text doctests:

This runs doctest examples inside any markdown file matching the glob. Useful for verifying that README code blocks still work, which is one of the better protections against rotting documentation.

Both flags can be configured permanently in pyproject.toml:

Or in pytest.ini:

With those in place, every pytest invocation includes the doctests. This is the setup most projects converge on: pytest as the test runner, doctest used selectively for documentation examples, both run together as one suite.

One caveat: --doctest-modules imports every Python file it finds, which can have side effects if modules do work at import time (printing, opening files, connecting to services). Most well-structured projects avoid those side effects, but a project that has them should exclude problematic files via pytest's collection options or by adding __test__ = False to the module.

Common Pitfalls

A handful of mistakes show up in almost every doctest suite.

Float Equality

The doctest >>> 0.1 + 0.2 prints 0.30000000000000004. The fixes:

Use rounding to a known precision and write the expected output with that precision. Alternatively, enable ELLIPSIS and write 0.300000.... Writing 0.3 will fail.

Dict and Set Order

Python 3.7+ preserves dict insertion order, but sets are still unordered, and dicts built from sets or from arbitrary iteration can have non-deterministic order. The safest pattern is to print sorted versions:

Sorting the items before checking guarantees deterministic output across versions and runs.

Object repr

When an example references an instance, the expected output is the repr of that instance. Change the __repr__ and every doctest mentioning it fails.

For a test like >>> Order(29.99) expecting Order(total=29.99), the test is now coupled to that exact format. Two ways out: assert on specific attributes (>>> Order(29.99).total followed by 29.99) instead of the whole repr, or enable ELLIPSIS and write Order(total=...).

Whitespace at End of Lines

A trailing space in the expected output, invisible in most editors, causes a doctest to fail with what looks like an identical comparison. Most editors have a "strip trailing whitespace on save" option. Enabling it is the cleanest fix. Otherwise, add # doctest: +NORMALIZE_WHITESPACE to the line.

Examples That Print Long Output

A function that prints a hundred-line report is a poor fit for doctest. The expected output dominates the docstring, makes the help unreadable, and breaks at any future tweak. Either trim the test (assert on len(output.splitlines()) or output.startswith(...)) or move the test to a real test file.

Tests That Modify Module State

Doctests in module-level docstrings share state with the rest of the module. An example that writes to a global variable or modifies a class attribute causes subsequent examples in the same module to see the change. Stick to local state (assign variables in the example, don't touch module-level names) unless that coupling is intentional.

When doctest Makes Sense

A summary of when to use doctest and when to pass on it.

Use doctest when:

  • Writing library API docs that need examples guaranteed to work.
  • A tutorial lives in a .md or .txt file with embedded code blocks.
  • A small utility function needs a low-ceremony way to lock in its behavior.
  • Contributing to a project (like CPython) that already uses doctest as its convention.

Skip doctest when:

  • The test requires setup, fixtures, mocks, or any kind of preparation that would clutter the docstring.
  • The output is long, multi-line, or sensitive to formatting noise.
  • The test needs parametrization, custom assertions, or rich diffs on failure.
  • The expected output depends on uncontrolled values (timestamps, ids, random data).

The model: doctest is a documentation tool that happens to also run tests. For documentation, doctest is excellent. For testing, dedicated testing frameworks are better.

A useful pattern in real projects is to mix both: doctest for the headline examples in each function's docstring (the ones a new user would see first), and pytest for the exhaustive coverage that catches edge cases. The --doctest-modules flag runs them together, providing both kinds of protection from a single pytest command.

Quiz

doctest Quiz

10 quizzes