Skip to content

Commit 3c06291

Browse files
committed
DOC: put pytest first in README; tweak it some more while at it
1 parent 5579471 commit 3c06291

File tree

2 files changed

+124
-99
lines changed

2 files changed

+124
-99
lines changed

README.md

Lines changed: 116 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ the output. Thus the example source needs to be valid python code still.
8181
## Install and test
8282

8383
```
84-
$ pip install -e .
84+
$ pip install scipy-doctest
8585
$ pytest --pyargs scipy_doctest
8686
```
8787

@@ -93,9 +93,56 @@ or nearly so.
9393

9494
The other layer is the `pytest` plugin.
9595

96+
### Run doctests via pytest
97+
98+
To run doctests on your package or project, follow these steps:
99+
100+
1. **Install the plugin**
101+
102+
```bash
103+
pip install scipy-doctest
104+
```
105+
106+
2. **Register or load the plugin**
107+
108+
Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
109+
110+
To do this, add the following line of code:
111+
112+
```python
113+
# In your conftest.py file or test module
114+
115+
pytest_plugins = "scipy_doctest"
116+
```
117+
118+
Check out the [pytest documentation](https://docs.pytest.org/en/stable/how-to/writing_plugins.html#requiring-loading-plugins-in-a-test-module-or-conftest-file) for more information on requiring/loading plugins in a test module or `conftest.py` file.
119+
120+
3. **Run doctests**
121+
122+
Once the plugin is registered, run the doctests by executing the following command:
123+
124+
```bash
125+
$ python -m pytest --doctest-modules
126+
```
127+
or
128+
```bash
129+
$ pytest --pyargs <your-package> --doctest-modules
130+
```
131+
132+
By default, all doctests are collected. To only collect public objects, `strategy="api"`,
133+
use the command flag
134+
135+
```bash
136+
$ pytest --pyargs <your-package> --doctest-modules --doctest-collect=api
137+
```
138+
139+
See [More fine-grained control](https://github.com/scipy/scipy_doctest#More-fine-grained-control) section
140+
for details on how to customize the behavior.
141+
96142

97143
### Basic usage
98144

145+
The use of `pytest` is optional, and you can use the `doctest` layer API.
99146
For example,
100147

101148
```
@@ -112,7 +159,8 @@ For more details, see the `testmod` docstring. Other useful functions are
112159
`find_doctests`, `run_docstring_examples` and `testfile` (the latter two mimic
113160
the behavior of the eponymous functions of the `doctest` module).
114161

115-
#### Command-line interface
162+
163+
### Command-line interface
116164

117165
There is a basic CLI, which also mimics that of the `doctest` module:
118166
```
@@ -127,8 +175,10 @@ Text files can also be CLI-checked:
127175
$ python -m scipy_doctest bar.rst
128176
```
129177

178+
Notice that the command-line usage only uses the default `DTConfig` settings.
179+
130180

131-
#### More fine-grained control
181+
## More fine-grained control
132182

133183
More fine-grained control of the functionality is available via the following
134184
classes
@@ -147,134 +197,104 @@ configuration is simply creating an instance, overriding an attribute and
147197
passing the instance to `testmod` or constructors of `DT*` objects. Defaults
148198
are provided, based on a long-term usage in SciPy.
149199

200+
See the [DTConfig docstring](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/impl.py#L24)
201+
for the full set of attributes that allow you to fine-tune your doctesting experience.
150202

151-
### The SciPy Doctest Pytest Plugin
203+
To set any of these attributes, create an instance of `DTConfig` and assign the attributes
204+
in a usual way.
152205

153-
The pytest plugin enables the use of `scipy_doctest` tools to perform doctests.
206+
If using the pytest plugin, it is convenient to use the default instance, which
207+
is predefined in `scipy_doctest/conftest.py`. This instance will be automatically
208+
passed around via an
209+
[attribute of pytest's `Config` object](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/plugin.py#L39).
154210

155-
Follow the given instructions to utilize the pytest plugin for doctesting.
211+
### Examples
156212

157-
### Running Doctests on SciPy
213+
```
214+
dt_config = DTConfig()
215+
```
158216

159-
1. **Install plugin**
217+
or, if using pytest,
160218

161-
```bash
162-
pip install scipy-doctest
219+
```python
220+
from scipy_doctest.conftest import dt_config # a DTConfig instance with default settings
163221
```
164222

165-
2. **Configure Your Doctesting Experience**
223+
and then
166224

167-
To tailor your doctesting experience, you can utilize an instance of `DTConfig`.
168-
An in-depth explanation is given in the [tailoring your doctesting experience](https://github.com/scipy/scipy_doctest#tailoring-your-doctesting-experience) section.
225+
```
226+
dt_config.rndm_markers = {'# unintialized'}
169227
170-
3. **Run Doctests**
228+
dt_config.stopwords = {'plt.', 'plt.hist', 'plt.show'}
171229
172-
Doctesting is configured to execute on SciPy using the `dev.py` module.
230+
dt_config.local_resources = {
231+
'scipy_doctest.tests.local_file_cases.local_files': ['scipy_doctest/tests/local_file.txt'],
232+
'scipy_doctest.tests.local_file_cases.sio': ['scipy_doctest/tests/octave_a.mat']
233+
}
173234
174-
To run all doctests, use the following command:
175-
```bash
176-
python dev.py smoke-docs
235+
dt_config.skiplist = {
236+
'scipy.special.sinc',
237+
'scipy.misc.who',
238+
'scipy.optimize.show_options'
239+
}
177240
```
178241

179-
To run doctests on specific SciPy modules, e.g: `cluster`, use the following command:
242+
If you don't set these attributes, the [default settings](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/impl.py#L94) of the attributes are used.
180243

181-
```bash
182-
python dev.py smoke-docs -s cluster
183-
```
184244

185-
### Running Doctests on Other Packages/Projects
245+
#### Alternative Checkers
186246

187-
If you want to run doctests on packages or projects other than SciPy, follow these steps:
247+
By default, we use the floating-point aware `DTChecker`. If you want to use an
248+
alternative checker, all you need to do is to define the corresponding class,
249+
and add an attribute to the `DTConfig` instance. For example,
188250

189-
1. **Install the plugin**
190251

191-
```bash
192-
pip install scipy-doctest
252+
```
253+
class VanillaOutputChecker(doctest.OutputChecker):
254+
"""doctest.OutputChecker to drop in for DTChecker.
255+
256+
LSP break: OutputChecker does not have __init__,
257+
here we add it to agree with DTChecker.
258+
"""
259+
def __init__(self, config):
260+
pass
193261
```
194262

195-
2. **Register or Load the Plugin**
263+
and
196264

197-
Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
265+
```
266+
dt_config = DTConfig()
267+
dt_config.CheckerKlass = VanillaOutputChecker
268+
```
198269

199-
To do this, add the following line of code:
270+
See [a pytest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/tests/test_pytest_configuration.py#L63)
271+
and [a doctest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/tests/test_runner.py#L94)
272+
for more details.
200273

201-
```python
202-
# In your conftest.py file or test module
203274

204-
pytest_plugins = "scipy_doctest"
205-
```
275+
### The SciPy Doctest Pytest Plugin
206276

207-
Check out the [pytest documentation](https://docs.pytest.org/en/stable/how-to/writing_plugins.html#requiring-loading-plugins-in-a-test-module-or-conftest-file) for more information on requiring/loading plugins in a test module or `conftest.py` file.
277+
The pytest plugin enables the use of `scipy_doctest` tools to perform doctests.
208278

209-
3. **Configure your doctesting experience**
279+
Follow the given instructions to utilize the pytest plugin for doctesting.
210280

211-
An in-depth explanation is given in the [tailoring your doctesting experience](https://github.com/scipy/scipy_doctest#tailoring-your-doctesting-experience) section.
281+
### NumPy and SciPy wrappers
212282

213-
4. **Run doctests**
214283

215-
Once the plugin is registered, you can run your doctests by executing the following command:
284+
NumPy wraps `scipy-doctest` with the `spin` command
216285

217-
```bash
218-
$ python -m pytest --doctest-modules
219286
```
220-
or
221-
```bash
222-
$ pytest --pyargs <your-package> --doctest-modules
287+
$ spin check-docs
223288
```
224289

225-
By default, all doctests are collected. To only collect public objects, `strategy="api"`,
226-
use the command flag
290+
SciPy wraps `scipy-doctest` with custom `dev.py` commands:
227291

228-
```bash
229-
$ pytest --pyargs <your-package> --doctest-modules --doctest-collect=api
230292
```
231-
232-
### Tailoring Your Doctesting Experience
233-
234-
[DTConfig](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/impl.py#L23) offers a variety of attributes that allow you to fine-tune your doctesting experience.
235-
236-
These attributes include:
237-
1. **default_namespace (dict):** Defines the namespace in which examples are executed.
238-
2. **check_namespace (dict):** Specifies the namespace for conducting checks.
239-
3. **rndm_markers (set):** Provides additional directives which act like `# doctest: + SKIP`.
240-
4. **atol (float) and rtol (float):** Sets absolute and relative tolerances for validating doctest examples.
241-
Specifically, it governs the check using `np.allclose(want, got, atol=atol, rtol=rtol)`.
242-
5. **optionflags (int):** These are doctest option flags.
243-
The default setting includes `NORMALIZE_WHITESPACE` | `ELLIPSIS` | `IGNORE_EXCEPTION_DETAIL`.
244-
6. **stopwords (set):** If an example contains any of these stopwords, the output is not checked (though the source's validity is still assessed).
245-
7. **pseudocode (list):** Lists strings that, when found in an example, result in no doctesting. This resembles the `# doctest +SKIP` directive and is useful for pseudocode blocks or similar cases.
246-
8. **skiplist (set):** A list of names of objects with docstrings known to fail doctesting and are intentionally excluded from testing.
247-
9. **user_context_mgr:** A context manager for running tests.
248-
Typically, it is entered for each DocTest (especially in API documentation), ensuring proper testing isolation.
249-
10. **local_resources (dict):** Specifies local files needed for specific tests. The format is `{test.name: list-of-files}`. File paths are relative to the path of `test.filename`.
250-
11. **parse_namedtuples (bool):** Determines whether to perform a literal comparison (e.g., `TTestResult(pvalue=0.9, statistic=42)`) or extract and compare the tuple values (e.g., `(0.9, 42)`). The default is `True`.
251-
12. **nameerror_after_exception (bool):** Controls whether subsequent examples in the same test, after one has failed, may raise spurious NameErrors. Set to `True` if you want to observe these errors or if your test is expected to raise NameErrors. The default is `False`.
252-
253-
To set any of these attributes, create an instance of `DTConfig` called `dt_config`.
254-
This instance is already set as an [attribute of pytest's `Config` object](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/plugin.py#L39).
255-
256-
**Example:**
257-
258-
```python
259-
from scipy_doctest import dt_config # a DTCongig instance with default settings
260-
261-
dt_config.stopwords = {'plt.', '.hist', '.show'}
262-
dt_config.local_resources = {
263-
'scipy_doctest.tests.local_file_cases.local_files': ['scipy_doctest/tests/local_file.txt'],
264-
'scipy_doctest.tests.local_file_cases.sio': ['scipy_doctest/tests/octave_a.mat']
265-
}
266-
dt_config.skiplist = {
267-
'scipy.special.sinc',
268-
'scipy.misc.who',
269-
'scipy.optimize.show_options'
270-
}
293+
$ python dev.py smoke-docs # check docstrings
294+
$ python dev.py smoke-tutorials # ReST user guide tutorials
271295
```
272296

273-
If you don't set these attributes, the [default settings](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/impl.py#L94) of the attributes are used.
274-
275-
By following these steps, you will be able to effectively use the SciPy Doctest pytest plugin for doctests in your Python projects.
276297

277-
Happy testing!
278298

279299
## Rough edges and sharp bits
280300

@@ -308,7 +328,7 @@ being optional. So you either guard the imports in doctests (yikes!), or
308328
the collections fails if dependencies are not available.
309329

310330
The solution is to explicitly `--ignore` the paths to modules with optionals.
311-
(or use `DTConfig.pytest_extra_ignore` list):
331+
(or, equivalently, use `DTConfig.pytest_extra_ignore` list):
312332

313333
```
314334
$ pytest --ignore=/build-install/lib/scipy/python3.10/site-packages/scipy/_lib ...
@@ -352,20 +372,17 @@ leads to
352372
differences are: (i) `pytest-doctestplus` is more sensitive to formatting,
353373
including whitespace---thus if numpy tweaks its output formatting, doctests
354374
may start failing; (ii) there is still a need for `# doctest: +FLOAT_CMP`
355-
directives; (iii) being a pytest plugin, `pytest-doctestplus` is tightly
356-
coupled to `pytest`. It thus needs to follow `pytest` releases, and
357-
some maintenance work may be required to adapt when `pytest` publishes a new
358-
release.
375+
directives.
359376

360377
This project takes a different approach: in addition to plugging into `pytest`,
361378
we closely follow the `doctest` API and implementation, which are naturally
362379
way more stable then `pytest`.
363380

364-
- `NumPy` and `SciPy` use modified doctesting in their `refguide-check` utilities.
381+
- `NumPy` and `SciPy` were using modified doctesting in their `refguide-check` utilities.
365382
These utilities are tightly coupled to their libraries, and have been reported
366383
to be not easy to reason about, work with, and extend to other projects.
367384

368-
This project is nothing but the core functionality of the modified
385+
This project is mainly the core functionality of the modified
369386
`refguide-check` doctesting, extracted to a separate package.
370387
We believe having it separate simplifies both addressing the needs of these
371388
two packages, and potential adoption by other projects.

scipy_doctest/impl.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ class DTConfig:
9191
adding `# may vary` to the outputs of all examples.
9292
Each key is a doctest name to skip, and the corresponding value is
9393
a string. If not empty, the string value is used as the skip reason.
94+
CheckerKlass : object, optional
95+
The class for the Checker object. Must mimic the ``DTChecker`` API:
96+
subclass the `doctest.OutputChecker` and make the constructor signature
97+
read ``__init__(self, config=None)``, where `config` is a ``DTConfig``
98+
instance.
99+
This class will be instantiated by ``DTRunner``.
100+
Defaults to `DTChecker`.
101+
94102
"""
95103
def __init__(self, *, # DTChecker configuration
96104
CheckerKlass=None,

0 commit comments

Comments
 (0)