Doctest runs code examples embedded in docstrings. Your documentation becomes your tests—and your tests become your documentation.

Basic Doctest

def add(a, b):
    """
    Add two numbers.
    
    >>> add(2, 3)
    5
    >>> add(-1, 1)
    0
    >>> add(0, 0)
    0
    """
    return a + b
 
if __name__ == '__main__':
    import doctest
    doctest.testmod()

Running Doctests

# Run doctest on a module
python -m doctest mymodule.py
 
# Verbose output
python -m doctest -v mymodule.py
 
# From within Python
import doctest
doctest.testmod(verbose=True)

Expected Output

def greet(name):
    """
    Greet someone.
    
    >>> greet('Alice')
    'Hello, Alice!'
    
    >>> greet('Bob')
    'Hello, Bob!'
    
    >>> print(greet('World'))
    Hello, World!
    """
    return f"Hello, {name}!"

Multi-line Output

def show_list(items):
    """
    Display items.
    
    >>> show_list(['a', 'b', 'c'])
    Items:
    - a
    - b
    - c
    """
    print("Items:")
    for item in items:
        print(f"- {item}")

Testing Exceptions

def divide(a, b):
    """
    Divide a by b.
    
    >>> divide(10, 2)
    5.0
    
    >>> divide(10, 0)
    Traceback (most recent call last):
        ...
    ZeroDivisionError: division by zero
    """
    return a / b

Ellipsis for Variable Output

def get_object_id(obj):
    """
    Get object id.
    
    >>> x = object()
    >>> get_object_id(x)  # doctest: +ELLIPSIS
    'Object at 0x...'
    """
    return f"Object at {hex(id(obj))}"
 
def show_dict(d):
    """
    Display dict (order may vary in older Python).
    
    >>> show_dict({'a': 1, 'b': 2})  # doctest: +ELLIPSIS
    {'a': 1, ...}
    """
    print(d)

Doctest Directives

def example():
    """
    >>> print("line 1")  # doctest: +NORMALIZE_WHITESPACE
    line   1
    
    >>> [1, 2, 3]  # doctest: +ELLIPSIS
    [1, ...]
    
    >>> raise ValueError("error")  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ValueError: ...
    
    >>> broken_function()  # doctest: +SKIP
    This won't run
    """
    pass

Common Directives

DirectiveEffect
+ELLIPSIS... matches anything
+NORMALIZE_WHITESPACEIgnore whitespace differences
+SKIPSkip this example
+IGNORE_EXCEPTION_DETAILMatch exception type only
+DONT_ACCEPT_TRUE_FOR_1Distinguish True from 1

Class Doctests

class Calculator:
    """
    A simple calculator.
    
    >>> calc = Calculator()
    >>> calc.add(2, 3)
    5
    >>> calc.memory
    5
    """
    
    def __init__(self):
        """
        Initialize calculator.
        
        >>> c = Calculator()
        >>> c.memory
        0
        """
        self.memory = 0
    
    def add(self, a, b):
        """
        Add two numbers and store in memory.
        
        >>> c = Calculator()
        >>> c.add(10, 20)
        30
        >>> c.memory
        30
        """
        result = a + b
        self.memory = result
        return result

Module-Level Doctest

"""
Math utilities module.
 
>>> from math_utils import square, cube
>>> square(4)
16
>>> cube(3)
27
 
You can perform chained operations:
>>> cube(square(2))
64
"""
 
def square(x):
    """Return x squared."""
    return x ** 2
 
def cube(x):
    """Return x cubed."""
    return x ** 3

Testing from File

# examples.txt
This file contains examples.
 
>>> from mymodule import add
>>> add(1, 2)
3
 
Another example:
 
>>> add(-1, -1)
-2
# Run it
import doctest
doctest.testfile('examples.txt')

Integrating with pytest

# conftest.py
import doctest
import mymodule
 
def test_doctests():
    results = doctest.testmod(mymodule)
    assert results.failed == 0
 
# Or using pytest-doctest plugin
# pytest --doctest-modules

Setup Code

def example():
    """
    Example with setup.
    
    First, create some data:
    >>> data = [1, 2, 3, 4, 5]
    >>> total = sum(data)
    
    Then use it:
    >>> total
    15
    >>> total / len(data)
    3.0
    """
    pass

Complex Output

def get_info():
    """
    Get system info.
    
    >>> info = get_info()
    >>> 'python_version' in info
    True
    >>> isinstance(info['python_version'], str)
    True
    
    Avoid testing exact values that may change:
    >>> len(info) > 0
    True
    """
    import sys
    return {
        'python_version': sys.version,
        'platform': sys.platform,
    }

Floating Point Comparison

def calculate_pi():
    """
    Calculate pi approximation.
    
    >>> pi = calculate_pi()
    >>> round(pi, 4)
    3.1416
    
    Or use comparison:
    >>> abs(pi - 3.14159) < 0.001
    True
    """
    return 3.14159265358979

Private Examples

def documented_function():
    """
    Public documentation.
    
    >>> documented_function()
    'result'
    """
    return 'result'
 
def _internal_helper():
    # No doctest here - implementation detail
    pass

TestCase from Doctest

import doctest
import unittest
import mymodule
 
def load_tests(loader, tests, ignore):
    tests.addTests(doctest.DocTestSuite(mymodule))
    return tests
 
# Now: python -m unittest discover

Doctest Options

import doctest
 
# Custom options
doctest.testmod(
    verbose=True,
    optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE
)
 
# Test specific function
doctest.run_docstring_examples(my_function, globals(), verbose=True)

Real-World Example

def parse_config(text: str) -> dict:
    """
    Parse KEY=VALUE config format.
    
    Basic usage:
    >>> parse_config("DEBUG=true")
    {'DEBUG': 'true'}
    
    Multiple lines:
    >>> config = '''
    ... HOST=localhost
    ... PORT=8080
    ... '''
    >>> result = parse_config(config)
    >>> result['HOST']
    'localhost'
    >>> result['PORT']
    '8080'
    
    Handles empty input:
    >>> parse_config("")
    {}
    
    Ignores comments:
    >>> parse_config("# comment\\nKEY=value")
    {'KEY': 'value'}
    """
    result = {}
    for line in text.strip().split('\n'):
        line = line.strip()
        if line and not line.startswith('#') and '=' in line:
            key, value = line.split('=', 1)
            result[key.strip()] = value.strip()
    return result

When to Use Doctest

Good for:

  • Simple functions with clear input/output
  • Documentation that should stay accurate
  • Quick sanity checks
  • Teaching and examples

Not ideal for:

  • Complex setup/teardown
  • Testing edge cases
  • Mocking dependencies
  • Performance-sensitive tests

Doctest turns your documentation into tests. Write examples that teach users and verify correctness simultaneously.

React to this post: