Skip to main content

Decorate `unittest.TestCase` to evaluate doctests with configurable globals and `setUp`/`tearDown`. Export to Markdown and reST to include in docs.

Project description

doctestcase

Decorate unittest.TestCase to evaluate doctests with configurable globals and setUp/tearDown. Format as Markdown and reST to include in docs.

license pypi python versions tests coverage tested with multipython uses docsub mypy uv Ruff

Features

  • Evaluate doctests
  • Configure doctest globals and setUp/tearDown
  • Relies on unittest.TestCase
  • Minimalistic decorator-based API
  • Format docstring as Markdown and reST to include in docs
  • Naturally fits docsub-based pipeline
  • No dependencies
  • Checked with mypy
  • 100% test coverage
  • Tested with Python 2.7+

Alternatives

  • doctest.DocTestSuite allows to run doctests with unittest, but individual doctests can't be extended, parametrized, or enclose with setUp/tearDown.

Installation

$ pip install doctestcase

Usage

Common use cases:

  • Decorated TestCase
  • Reuse __doctestcase__ from other TestCase
  • Parametrize test case
  • Inherit from decorated TestCase
  • Format docstring as Markdown or reStructuredText
  • Integration with docsub

For @doctestcase details, check API Reference.

Decorated TestCase

from doctest import ELLIPSIS
from unittest import TestCase

from doctestcase import doctestcase


@doctestcase(globals={'X': 'yz'}, options=ELLIPSIS)
class SimpleCase(TestCase):
    """
    Title

    Paragraph.

    >>> X * 100
    'yzyz...'

    Another paragraph.

    >>> None
    >>> True
    True
    """

    def test_custom(self):  # called before 'test_docstring'
        self.assertTrue(True)

    def test_other(self):  # called after 'test_docstring'
        self.assertTrue(True)

All test methods are called by unittest in alphabetic order, including test_docstring, added by @doctestcase.

Reuse __doctestcase__ from other TestCase

Extending example above,

@SimpleCase.__doctestcase__
class AnotherCase(TestCase):
    """
    Title

    >>> X * 100
    'yzyz...'
    """

Now AnotherCase.__doctestcase__ holds shallow copy of globals, kwargs, and same doctest options, as SimpleCase. These copies are independent.

Inherit from decorated class

Inheriting from another test case decorated with @doctestcase allows to reuse and extend globals and kwargs and override doctest options of the base class.

Extending examples above,

@doctestcase(globals={'A': 'bc'})
class InheritedCase(SimpleCase):
    """
    Title

    >>> (X + A) * 100
    'yzbcyzbc...'
    """

Notice that global variable A was added to globals defined in SimpleCase, and the new class reuses doctest.ELLIPSIS option.

For more details on how doctestcase properties are updated, check the API Reference.

Parametrize doctest case

First, define base class parametrized with cwd:

from doctest import ELLIPSIS
import os.path
import shutil
import tempfile
from unittest import TestCase, skip

from doctestcase import doctestcase


@skip('base class')
@doctestcase(options=ELLIPSIS, cwd='.')
class ChdirTestCase(TestCase):
    def setUp(self):
        self.temp = tempfile.mkdtemp()
        self.prev = os.getcwd()
        cwd = os.path.join(self.temp, self.__doctestcase__.kwargs['cwd'])
        if not os.path.exists(cwd):
            os.mkdir(cwd)
        os.chdir(cwd)

    def tearDown(self):
        os.chdir(self.prev)
        shutil.rmtree(self.temp)

Notice how the base class is skipped from testing.

In this example we use os.path module for compatibility with older Python versions only. If you use recent Python versions, use pathlib instead.

Now we can define test case parametrized with cwd:

@doctestcase(cwd='subdir')
class Case1(ChdirTestCase):
    """
    >>> import os
    >>> os.getcwd()
    '.../subdir'
    """

Inherit from decorated TestCase

Test cases, decorated with @doctestcase, can be used as base classes for other test cases. This is useful when inherited classes need to extend or change properties, passed to parent's @doctestcase. Parent properties will be copied and updated with values from child class decorator.

For the SimpleCase class above,

@doctestcase(globals={'A': 'bc'})
class InheritedCase(SimpleCase):
    """
    Title

    >>> (X + A) * 100
    'yzbcyzbc...'
    """

Format docstring as Markdown or reStructuredText

For the SimpleCase class above,

Markdown

>>> from doctestcase import to_markdown
>>> to_markdown(SimpleCase)
## Title

Paragraph.

```pycon
>>> X * 100
'yzyz...'
```

Another paragraph.

```pycon
>>> None
>>> True
True
```

reStructuredText

>>> from doctestcase import to_rest
>>> to_rest(SimpleCase)
Title
-----

Paragraph.

>>> X * 100
'yzyz...'

Another paragraph.

>>> None
>>> True
True

Integration with docsub

When documenting packages, "Usage" section often includes doctests. It is a good practice to test all documented use cases, so why not adopt test-driven documenting approach and write tests with docs in mind?

  1. Write tests with carefully crafted docstrings using doctests.
  2. Include generated Markdown or reST in docs.

With docsub, this can be achieved with some minimal configuration.

Just two commands to run tests and update docs:

$ pytest tests
$ docsub sync -i usage.md

usage.md

# Usage

<!-- docsub: begin -->
<!-- docsub: x case tests/test_usage.py:UseCase1 -->
## Use Case 1

Long description of the use case.

Usage example in doctest:

```pycon
>>> True
True
```
<!-- docsub: end -->

tests/test_usage.py

from unittest import TestCase

from doctestcase import doctestcase


@doctestcase()
class UseCase1(TestCase):
    """
    Use Case 1

    Long description of the use case.

    Usage example in doctest:

    >>> True
    True
    """

docsubfile.py

Docsub configuration file declaring project-local x-tension command:

from docsub import click
from doctestcase import to_markdown
from importloc import Location


@click.group()
def x() -> None:
    pass


@x.command()
@click.argument('case')
def case(case: str) -> None:
    text = to_markdown(Location(case).load(), title_depth=2)
    click.echo(text, nl=False)

See also

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

doctestcase-0.1.0.tar.gz (91.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

doctestcase-0.1.0-py2.py3-none-any.whl (9.7 kB view details)

Uploaded Python 2Python 3

File details

Details for the file doctestcase-0.1.0.tar.gz.

File metadata

  • Download URL: doctestcase-0.1.0.tar.gz
  • Upload date:
  • Size: 91.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.13

File hashes

Hashes for doctestcase-0.1.0.tar.gz
Algorithm Hash digest
SHA256 562d2a6cb045c06dafd4271dd91e66b4c2279af31850ba70de3fd550a83ad84b
MD5 6695ec8466bacdee73ead27dec32b167
BLAKE2b-256 7e707bcf93d478b6def297d5eb9aa5b34f8eb1587969c44be3bcd5efccc6348c

See more details on using hashes here.

File details

Details for the file doctestcase-0.1.0-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for doctestcase-0.1.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 6c0aba226ef1902f1edbd6b95d7536654d928470be9457a1472ccd8b9009d6b2
MD5 d13843cd8cf4911e69811139caadfc6f
BLAKE2b-256 28da8eaca238436c3df57dc6795f998f57cb466f051cbbad1476b919ccd416a1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page