doctest-tests
Configures and runs Python's stdlib doctest - embeds executable test cases in docstrings using `>>>` Python interactive prompt convention; supports `# doctest: +ELLIPSIS` / `+NORMALIZE_WHITESPACE` / `+SKIP` directives; integrates with pytest via `--doctest-modules` flag; runs as `python -m doctest module.py -v`. Use for self-documenting reference implementations + simple smoke-test coverage embedded in API docs.
Install with skills.sh (any agent)
npx skills add testland/qa --skill doctest-testsdoctest-tests
Overview
Per docs.python.org/3/library/doctest.html (opens in new window):
doctest is Python's stdlib facility for embedding executable examples in docstrings. The interactive-prompt convention (>>> ... for input; expected output on the next line) becomes a test case automatically.
Distinguishing properties:
When to use
For non-trivial test suites, prefer pytest-tests. Use doctest as a complement (smoke + docs), not replacement.
Step 1 - Basic doctest
def sum(a, b):
"""Add two numbers.
>>> sum(1, 2)
3
>>> sum(0, 0)
0
>>> sum(-1, 1)
0
"""
return a + bRun:
python -m doctest module.py
python -m doctest module.py -v # verbose; show all examplesPass if the doctest output matches; fail if mismatch.
Step 2 - Multi-line + intermediate state
def fibonacci(n):
"""Compute Fibonacci numbers iteratively.
>>> fibonacci(0)
0
>>> fibonacci(1)
1
>>> fibonacci(10)
55
>>> [fibonacci(n) for n in range(8)]
[0, 1, 1, 2, 3, 5, 8, 13]
"""
if n < 2: return n
a, b = 0, 1
for _ in range(n - 1):
a, b = b, a + b
return bStep 3 - Directives
Per dt-docs (opens in new window):
| Directive | Use |
|---|---|
# doctest: +ELLIPSIS | ... matches arbitrary substrings |
# doctest: +NORMALIZE_WHITESPACE | Collapse whitespace before compare |
# doctest: +SKIP | Skip this example |
# doctest: +IGNORE_EXCEPTION_DETAIL | Ignore exception message detail (just match exception type) |
# doctest: +DONT_ACCEPT_TRUE_FOR_1 | Strict bool != int comparison |
# doctest: -ELLIPSIS | Disable a directive |
Examples:
def list_users():
"""Return user records.
>>> list_users() # doctest: +ELLIPSIS
[{'id': 1, 'name': 'Alice', 'created_at': ...}, ...]
>>> list_users() # doctest: +NORMALIZE_WHITESPACE
[{'id': 1, 'name': 'Alice'}]
"""
return [...]
def buggy_function():
"""Raise an error.
>>> buggy_function() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ValueError
"""
raise ValueError("specific message that may change")Step 4 - Expected 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 / bThe Traceback... + ... ellipsis + exception line pattern is specific to doctest expected-error format.
Step 5 - pytest integration
pip install pytest
pytest --doctest-modules src/Per pytest docs, --doctest-modules collects doctests from all modules in the source tree. Combine with regular pytest:
pytest --doctest-modules tests/Both regular tests + doctests run in one pass.
In pyproject.toml:
[tool.pytest.ini_options]
addopts = "--doctest-modules"Step 6 - Sphinx integration
sphinx.ext.doctest runs doctests during Sphinx HTML build:
# docs/conf.py
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest']
doctest_global_setup = """
import mymodule
"""sphinx-build -b doctest docs/ build/doctest/This catches docs that drift from the implementation - examples in RST/MD docs are also doctest-runnable.
Step 7 - When doctest is the WRONG choice
doctest is not for:
For those, use pytest-tests.
Step 8 - CI integration
- run: pip install pytest
- run: pytest --doctest-modules src/
# Or stdlib-only:
- run: python -m doctest src/*.py -vAnti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Use doctest for complex logic | Docstrings become unreadable | Use pytest for non-trivial tests; doctest for examples |
| Show non-deterministic output (timestamps) without ELLIPSIS | Test fails on every run | Use +ELLIPSIS directive (Step 3) |
| Show dict output in older Python (pre-3.7) | Order varies; fails | Sort first or use Python 3.7+ insertion-order dict |
Forget Traceback (most recent call last): pattern | Exception expectation doesn't match | Follow exact format (Step 4) |
| Skip Sphinx integration for documented examples | Docs drift from impl | sphinx.ext.doctest (Step 6) |
Limitations
References
Related skills
nose2-tests
Configures and runs nose2 - successor to nose (the original Python test discovery library, end-of-life 2015) and an alternative to pytest's discovery model; supports plugin architecture, layers (per-test-class setUp/tearDown shared across modules), parameterized tests via `nose2.tools.params`, multi-process parallelism via mp plugin. Use when migrating from legacy nose1 codebases or when the team prefers nose2's plugin model over pytest.
pytest-asyncio-patterns
Configures and runs async Python tests with pytest-asyncio: installs the plugin, selects asyncio_mode (auto vs strict), scopes event loops (function/class/module/session), writes async fixtures with @pytest_asyncio.fixture, mocks coroutines with AsyncMock, and tests FastAPI (httpx.AsyncClient + ASGITransport) and aiohttp (aiohttp_client fixture) applications. Use when a Python project contains async def test_ functions, FastAPI/aiohttp endpoints, or any asyncio-based code that needs pytest integration. Do NOT use for general pytest fixture design, parametrize patterns, or conftest.py structure without an asyncio-specific problem (event-loop scoping, mode config, AsyncMock, ASGI client): use pytest-tests for those.
pytest-tests
Configures and runs pytest - the de facto Python test framework with fixture-based dependency injection (`@pytest.fixture` with scopes module/session/function), parametrize for table-driven tests (`@pytest.mark.parametrize`), markers (`@pytest.mark.skip` / `xfail` / `slow`), `conftest.py` for shared fixtures, plugin ecosystem (pytest-cov, pytest-asyncio, pytest-mock, pytest-xdist), `--lf`/`--ff` for fail-loop, coverage gating. Use when working with Python and needing the modern test framework.
unittest-tests
Configures and runs Python's stdlib unittest - TestCase + setUp/tearDown lifecycle hooks, assertion catalog (assertEqual / assertRaises / assertIn / assertAlmostEqual), unittest.mock module (Mock / MagicMock / patch / patch.object / patch.dict), test discovery via `python -m unittest discover`, subTest for parametrized cases, expectedFailure decorator. Use when constrained to stdlib-only (no pip install) or migrating legacy unittest codebases.