https://github.com/makukha/doctestcase
Evaluate doctests with configurable globals and `setUp`–`tearDown`. Export to Markdown and reST to include in docs.
https://github.com/makukha/doctestcase
doctest markdown restructuredtext rst test-case unittest
Last synced: 6 months ago
JSON representation
Evaluate doctests with configurable globals and `setUp`–`tearDown`. Export to Markdown and reST to include in docs.
- Host: GitHub
- URL: https://github.com/makukha/doctestcase
- Owner: makukha
- License: mit
- Created: 2025-02-07T15:44:25.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2025-04-03T11:13:52.000Z (6 months ago)
- Last Synced: 2025-04-03T12:22:04.487Z (6 months ago)
- Topics: doctest, markdown, restructuredtext, rst, test-case, unittest
- Language: Python
- Homepage: https://doctestcase.readthedocs.io
- Size: 170 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: .github/CONTRIBUTING.md
- License: LICENSE
- Code of conduct: .github/CODE_OF_CONDUCT.md
- Security: .github/SECURITY.md
Awesome Lists containing this project
README
# doctestcase
> Evaluate doctests with configurable globals and `setUp`–`tearDown`. Export to Markdown and reST to include in docs.
[](https://github.com/makukha/doctestcase/blob/main/LICENSE)
[](https://pypi.org/project/doctestcase)
[](https://pypi.org/project/doctestcase)
[](https://github.com/makukha/doctestcase)
[](https://github.com/makukha/doctestcase)
[](https://github.com/makukha/multipython)
[](https://github.com/makukha/docsub)
[](http://mypy.readthedocs.io)
[](https://github.com/astral-sh/ruff)
[](https://github.com/astral-sh/ruff)
[](https://www.bestpractices.dev/projects/10377)# Features
* Combines `unittest.TestCase` and `doctest`
* Inject globals into doctests
* Mock doctests
* Respect `setUp()` and `tearDown()`
* Minimalistic decorator-based API
* Format docstring as Markdown and reST to include in docs
* Naturally fits [docsub](https://github.com/makukha/docsub)-based pipeline
* No dependencies
* Checked with mypy
* 100% test coverage
* Tested with Python 2.7+# Installation
```shell
$ pip install doctestcase
```# Usage
* Decorated `TestCase`
* Parametrize test case
* Reuse `__doctestcase__` from other `TestCase`
* Inherit from decorated `TestCase`
* Format docstring as Markdown or reStructuredText
* Integration with [docsub](https://github.com/makukha/docsub)See [API Reference](https://doctestcase.readthedocs.io/en/latest/api.html) for details.
### Decorated `TestCase`
```python
from doctest import ELLIPSIS
from unittest import TestCasefrom doctestcase import doctestcase
@doctestcase(globals={'X': 'yz'}, options=ELLIPSIS)
class SimpleCase(TestCase):
"""
TitleParagraph.
>>> 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`.
### Mock doctests
In classes decorated with `@doctestcase`, `unittest.mock` patches apply to doctests
too, if patching is applied above `@doctestcase()` decorator. For Python below 3.3,
use [mock](https://pypi.org/project/mock/) package instead.````python
import time
from unittest import TestCase, mockfrom doctestcase import doctestcase
@mock.patch('time.time', mock.MagicMock(return_value=0))
@doctestcase()
class WithPatchedTime(TestCase):
"""
Mocking modules in doctests and testcase methods>>> import time
>>> time.time()
0
"""def test_method(self):
self.assertEqual(0, time.time())
````### Parametrize doctest case
First, define base class parametrized with `cwd`:
````python
from doctest import ELLIPSIS
import os.path
import shutil
import tempfile
from unittest import TestCasefrom doctestcase import doctestcase
@doctestcase(options=ELLIPSIS, cwd='.')
class ChdirTestCase(TestCase):
def setUp(self):
if self.__class__ is ChdirTestCase:
self.skipTest('base class') # no tests of the base class itself
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`:
````python
@doctestcase(cwd='subdir')
class Case1(ChdirTestCase):
"""
>>> import os
>>> os.getcwd()
'.../subdir'
"""
````### Reuse `__doctestcase__` from other `TestCase`
Extending example above,
```python
@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 `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,
````python
@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](https://doctestcase.readthedocs.io/en/latest/api.html).
### Format docstring as Markdown or reStructuredText
For the `SimpleCase` class above,
#### Markdown
```pycon
>>> from doctestcase import to_markdown
>>> to_markdown(SimpleCase)
```````markdown
## TitleParagraph.
```pycon
>>> X * 100
'yzyz...'
```Another paragraph.
```pycon
>>> None
>>> True
True
```
````#### reStructuredText
```pycon
>>> from doctestcase import to_rest
>>> to_rest(SimpleCase)
```````restructuredtext
Title
-----Paragraph.
>>> X * 100
'yzyz...'Another paragraph.
>>> None
>>> True
True
````### Integration with [docsub](https://github.com/makukha/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](https://github.com/makukha/docsub), this can be achieved with some minimal configuration.
Just two commands to run tests and update docs:
```shell
$ pytest tests
$ docsub sync -i usage.md
```#### usage.md
````markdown
# Usage## Use Case 1
Long description of the use case.
Usage example in doctest:
```pycon
>>> True
True
```````
#### tests/test_usage.py
````python
from unittest import TestCasefrom doctestcase import doctestcase
@doctestcase()
class UseCase1(TestCase):
"""
Use Case 1Long description of the use case.
Usage example in doctest:
>>> True
True
"""
````#### docsubfile.py
Docsub configuration file declaring project-local x-tension command:
````python
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)
````# Contributing
Pull requests, feature requests, and bug reports are welcome!
* [Contribution guidelines](https://github.com/makukha/doctestcase/blob/main/.github/CONTRIBUTING.md)
# Authors
* Michael Makukha
# See also
* [Documentation](https://doctestcase.readthedocs.io)
* [Issues](https://github.com/makukha/doctestcase/issues)
* [Changelog](https://github.com/makukha/doctestcase/blob/main/CHANGELOG.md)
* [Security Policy](https://github.com/makukha/doctestcase/blob/main/.github/SECURITY.md)
* [Contribution Guidelines](https://github.com/makukha/doctestcase/blob/main/.github/CONTRIBUTING.md)
* [Code of Conduct](https://github.com/makukha/doctestcase/blob/main/.github/CODE_OF_CONDUCT.md)
* [License](https://github.com/makukha/doctestcase/blob/main/LICENSE)