Safeguard
Application Security•

How do you write tests in Python using doctest?

Doctest runs the examples in your docstrings and checks the output. Learn the syntax, the ELLIPSIS option, how to run it and where it stops being enough.

Safeguard Security Team
5 min read

Doctest is a Python standard library module that finds interactive-session examples in docstrings, runs them, and checks that the printed output matches what you wrote. To use it, add examples with >>> prompts to a docstring and run python -m doctest -v yourfile.py. It keeps documentation honest and suits small, pure functions.

How does doctest work?

The Python documentation says the module searches for text that looks like interactive Python sessions and executes them to verify they work exactly as shown. Three uses are named: checking that docstrings are up to date, regression testing from a test file or object, and writing tutorial documentation.

A first example:

def clamp(value, low, high):
    """Limit value to the range low..high.

    >>> clamp(5, 0, 10)
    5
    >>> clamp(-3, 0, 10)
    0
    >>> clamp(99, 0, 10)
    10
    """
    return max(low, min(value, high))

Run it with python -m doctest -v clamp.py. The verbose flag prints each example tried and a summary. Without the flag, a passing run prints nothing, which surprises people the first time.

Why is this useful for security work?

A security-relevant function is often small and easy to get subtly wrong: input validation, path joining, escaping, token comparison. Examples that include hostile input make the intent explicit.

def safe_name(name):
    """Return name if it is a plain file name, else raise ValueError.

    >>> safe_name("report.txt")
    'report.txt'
    >>> safe_name("../etc/passwd")
    Traceback (most recent call last):
        ...
    ValueError: not a plain file name
    """
    if "/" in name or "\\" in name or name in ("", ".", ".."):
        raise ValueError("not a plain file name")
    return name

The traceback example is a documented doctest pattern: the header line and the final exception line matter, and the indented middle can be elided with .... The next person who changes the function sees the rejected case in the docstring, and the test fails if they break it.

What are the common gotchas?

Output must match exactly, including whitespace and the repr of values. A dictionary or set printed in a different order will fail. Sort before printing, or compare with == and show True.

Floating-point output is another trap. Show a rounded value rather than a long decimal.

For output that varies, such as memory addresses or timestamps, the ELLIPSIS option lets ... in the expected output match any substring. The documentation notes that it can match across lines and empty strings too, so use it narrowly. Add it with a directive comment on the example line.

Examples also share a namespace per docstring, so one example can build on a variable from an earlier one. That is handy for tutorials and fragile for tests. Keep each docstring independent.

When is doctest not enough?

Doctest is not a replacement for a full test framework. It does not give you fixtures, parametrization, or fine-grained failure output, and long docstrings become hard to read. It also is not a fuzzer. A handful of hand-picked cases will not find the input nobody thought of.

A sensible split: doctest for the short examples that explain a function, and a conventional test suite for edge cases, integration, and security regressions. Python's unittest can discover doctests too, so you can run both together.

How do you run doctest in other ways?

You can also run examples from inside a module by calling doctest.testmod() under the usual if __name__ == "__main__" check, which makes the file self-testing when executed directly. And you can keep examples in a plain text file and run them with doctest.testfile, which suits tutorials and README files where prose and examples sit together.

In a team, the simplest habit is a CI line that runs python -m doctest over the modules that carry examples. A failing example then blocks a merge before stale documentation reaches a reader. Keep each example short so a failure message points straight at the line that changed.

How to check or apply this

  1. Add two or three examples to each small public function, including one failure case.
  2. Run python -m doctest -v module.py locally, and add it to CI.
  3. Make output deterministic: sort collections, round floats, avoid addresses.
  4. Use ELLIPSIS only where output truly varies.
  5. Keep a separate test suite for edge cases and for every security bug you fix.
  6. Treat a failing doctest as either a code bug or stale documentation, and fix whichever it is.

Quick check

Why does doctest print nothing when tests pass?

By design. Silence means every example matched. Add the verbose flag, -v, to see each example and a summary.

Can doctest check that an exception is raised?

Yes. Write the traceback header, an indented ellipsis, and the final exception line. Doctest compares the exception type and message.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.