Как запускать doctests
По умолчанию все файлы, соответствующие шаблону test*.txt, будут выполняться через стандартный модуль Python doctest. Шаблон можно изменить, выполнив:
pytest --doctest-glob="*.rst"
в командной строке. Опцию --doctest-glob можно указывать несколько раз.
Если затем у вас есть текстовый файл, например такой:
# content of test_example.txt
hello this is a doctest
>>> x = 3
>>> x
3
то можно просто вызвать pytest:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-9.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
test_example.txt . [100%]
============================ 1 passed in 0.12s =============================
По умолчанию pytest собирает (collect) файлы test*.txt и ищет директивы doctest, но вы можете передать дополнительные шаблоны glob через опцию --doctest-glob (можно указывать несколько раз).
Помимо текстовых файлов, можно выполнять doctest прямо из docstring ваших классов и функций, включая docstring из тестовых модулей:
# content of mymodule.py
def something():
"""a doctest in a docstring
>>> something()
42
"""
return 42
$ pytest --doctest-modules
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-9.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
mymodule.py . [ 50%]
test_example.txt . [100%]
============================ 2 passed in 0.12s =============================
Эти изменения можно сделать постоянными для проекта, добавив их в конфигурационный файл, например так:
# content of pytest.toml
[pytest]
addopts = ["--doctest-modules"]
Кодировка
Кодировка по умолчанию — UTF-8, но можно указать кодировку, которая будет использоваться для doctest-файлов, через опцию конфигурации doctest_encoding:
toml
[pytest]
doctest_encoding = "latin1"
ini
[pytest]
doctest_encoding = latin1
Использование опций ‘doctest’
Стандартный модуль Python doctest предоставляет некоторые опции, которые задают «строгость» doctest-тестов. В pytest эти флаги можно включить через конфигурационный файл.
Например, чтобы игнорировать пробелы в конце строк и игнорировать длинные traceback исключений, можно написать:
toml
[pytest]
doctest_optionflags = ["NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"]
ini
[pytest]
doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL
Также опции можно включать inline-комментарием прямо в doctest:
>>> something_that_raises() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
ValueError: ...
pytest также вводит новые опции:
ALLOW_UNICODE: если включено, префикс u удаляется из unicode-строк в ожидаемом выводе doctest. Это позволяет запускать doctest одинаково в Python 2 и Python 3.
ALLOW_BYTES: аналогично, префикс b удаляется из байтовых строк в ожидаемом выводе doctest.
NUMBER: если включено, числа с плавающей точкой должны совпадать только до точности, указанной вами в ожидаемом выводе doctest. Числа сравниваются через pytest.approx() с относительным допуском, равным точности. Например, следующий вывод должен совпасть только до 2 десятичных знаков при сравнении 3.14 с pytest.approx(math.pi, rel=10**-2):
>>> math.pi
3.14
Если бы вы написали 3.1416, то фактический вывод должен был бы совпасть примерно до 4 десятичных знаков; и так далее.
Это помогает избежать ложных срабатываний из‑за ограниченной точности floating point, например:
Expected:
0.233
Got:
0.23300000000000001
NUMBER также поддерживает списки чисел с плавающей точкой — фактически он находит числа с плавающей точкой в любом месте вывода, даже внутри строки! Поэтому включать его глобально в doctest_optionflags в конфигурации может быть неуместно.
Добавлено в версии 5.1.
Продолжение после падения
По умолчанию pytest сообщает только о первом падении для заданного doctest. Если вы хотите продолжить выполнение теста даже при ошибках, используйте:
pytest --doctest-modules --doctest-continue-on-failure
Формат вывода
Можно изменить формат diff-вывода при падении doctest, используя один из стандартных форматов модуля doctest (см. doctest.REPORT_UDIFF, doctest.REPORT_CDIFF, doctest.REPORT_NDIFF, doctest.REPORT_ONLY_FIRST_FAILURE):
pytest --doctest-modules --doctest-report none
pytest --doctest-modules --doctest-report udiff
pytest --doctest-modules --doctest-report cdiff
pytest --doctest-modules --doctest-report ndiff
pytest --doctest-modules --doctest-report only_first_failure
Возможности, специфичные для pytest
Есть некоторые возможности, которые упрощают написание doctest или улучшают интеграцию с вашим набором тестов. Однако имейте в виду: используя эти возможности, вы сделаете doctest несовместимыми со стандартным модулем doctests.
Использование фикстур
Можно использовать фикстуры через хелпер getfixture:
# content of example.rst
>>> tmp = getfixture('tmp_path')
>>> ...
>>>
Обратите внимание: фикстура должна быть определена в месте, доступном pytest, например в conftest.py или плагине. Обычные python-файлы с docstring по умолчанию не сканируются на фикстуры, если это явно не настроено опцией python_files.
Также маркер usefixtures и фикстуры с опцией autouse поддерживаются при выполнении текстовых doctest-файлов.
Фикстура ‘doctest_namespace’
Фикстура doctest_namespace позволяет внедрять объекты в пространство имён, в котором выполняются doctest. Она предназначена для использования внутри ваших фикстур, чтобы давать тестам контекст.
doctest_namespace — это обычный объект dict, в который нужно поместить объекты, которые должны быть доступны в пространстве имён doctest:
# content of conftest.py
import pytest
import numpy
@pytest.fixture(autouse=True)
def add_np(doctest_namespace):
doctest_namespace["np"] = numpy
После этого ими можно пользоваться в doctest напрямую:
# content of numpy.py
def arange():
"""
>>> a = np.arange(10)
>>> len(a)
10
"""
Как и в обычном conftest.py, фикстуры обнаруживаются в дереве каталогов, где находится conftest. Это означает, что если doctest лежит рядом с исходным кодом, соответствующий conftest.py должен находиться в том же дереве каталогов. Фикстуры не будут обнаруживаться в «соседнем» дереве каталогов!
Пропуск тестов
По тем же причинам, по которым пропускают обычные тесты, можно пропускать тесты внутри doctest.
Чтобы пропустить одну проверку внутри doctest, можно использовать стандартную директиву doctest.SKIP:
def test_random(y):
"""
>>> random.random() # doctest: +SKIP
0.156231223
>>> 1 + 1
2
"""
Это пропустит первую проверку, но не вторую.
pytest также позволяет использовать стандартные функции pytest pytest.skip() и pytest.xfail() внутри doctest. Это может быть полезно, потому что можно пропускать/xfail тесты в зависимости от внешних условий:
>>> import sys, pytest
>>> if sys.platform.startswith('win'):
... pytest.skip('this doctest does not work on Windows')
...
>>> import fcntl
>>> ...
Однако использование этих функций не рекомендуется, потому что оно снижает читаемость docstring.
Note
pytest.skip() и pytest.xfail() ведут себя по‑разному в зависимости от того, где находятся doctest:
Python modules (docstrings): функции действуют только в этом конкретном docstring, позволяя другим docstring в том же модуле выполниться как обычно.
Text files: функции пропускают/xfail проверки до конца всего файла.
Альтернативы
Хотя встроенная поддержка pytest даёт хороший набор функций для doctest, при активном использовании doctest могут быть интересны внешние пакеты, которые добавляют больше возможностей и интеграцию с pytest:
pytest-doctestplus: расширенная поддержка doctest и тестирование файлов reStructuredText (“.rst”).
Sybil: способ тестировать примеры в документации, парся их из исходников документации и выполняя как часть обычного запуска тестов.