Testland
Browse all skills & agents

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-tests
View source

doctest-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:

  • Self-documenting: examples in docstrings render in help() and Sphinx HTML.
  • Stdlib: no install needed.
  • Limited scope: best for simple smoke tests + API reference; not a replacement for pytest.

When to use

  • Library / utility code where API docs include usage examples.
  • Smoke tests for documented behavior (verifying docs don't drift from implementation).
  • Sphinx-rendered documentation where examples should be guaranteed to work.
  • Simple modules where the doctest IS the test suite.

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 + b

Run:

python -m doctest module.py
python -m doctest module.py -v   # verbose; show all examples

Pass 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 b

Step 3 - Directives

Per dt-docs (opens in new window):

DirectiveUse
# doctest: +ELLIPSIS... matches arbitrary substrings
# doctest: +NORMALIZE_WHITESPACECollapse whitespace before compare
# doctest: +SKIPSkip this example
# doctest: +IGNORE_EXCEPTION_DETAILIgnore exception message detail (just match exception type)
# doctest: +DONT_ACCEPT_TRUE_FOR_1Strict bool != int comparison
# doctest: -ELLIPSISDisable 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 / b

The 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:

  • Tests with shared expensive setup (no fixtures)
  • Parametrized tests across many cases (verbose)
  • Tests with non-deterministic output (timestamps, IDs, dicts in Python <3.7 with arbitrary ordering)
  • Mocking external systems (no built-in mock)
  • Coverage measurement (works with coverage run -m doctest but rougher than pytest-cov)

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 -v

Anti-patterns

Anti-patternWhy it failsFix
Use doctest for complex logicDocstrings become unreadableUse pytest for non-trivial tests; doctest for examples
Show non-deterministic output (timestamps) without ELLIPSISTest fails on every runUse +ELLIPSIS directive (Step 3)
Show dict output in older Python (pre-3.7)Order varies; failsSort first or use Python 3.7+ insertion-order dict
Forget Traceback (most recent call last): patternException expectation doesn't matchFollow exact format (Step 4)
Skip Sphinx integration for documented examplesDocs drift from implsphinx.ext.doctest (Step 6)

Limitations

  • Whitespace + output formatting is exact-match by default.
  • No fixtures / setup beyond module-level state.
  • Test discovery is name-based (functions / classes with docstrings containing >>>).
  • Mocking requires breaking out of the docstring (defeats the purpose).

References

  • dt-docs (opens in new window) - official doctest reference
  • docs.pytest.org/en/stable/how-to/doctest.html - pytest --doctest-modules
  • sphinx-doc.org/en/master/usage/extensions/doctest.html - Sphinx integration
  • pytest-tests, unittest-tests, nose2-tests - sister tools
  • test-code-conventions - test code hygiene

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.