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 / bEllipsis 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
"""
passCommon Directives
| Directive | Effect |
|---|---|
+ELLIPSIS | ... matches anything |
+NORMALIZE_WHITESPACE | Ignore whitespace differences |
+SKIP | Skip this example |
+IGNORE_EXCEPTION_DETAIL | Match exception type only |
+DONT_ACCEPT_TRUE_FOR_1 | Distinguish 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 resultModule-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 ** 3Testing 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-modulesSetup 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
"""
passComplex 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.14159265358979Private Examples
def documented_function():
"""
Public documentation.
>>> documented_function()
'result'
"""
return 'result'
def _internal_helper():
# No doctest here - implementation detail
passTestCase 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 discoverDoctest 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 resultWhen 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: