doctest — Проверка интерактивных примеров Python
Исходный код: Lib/doctest.py
Модуль doctest ищет фрагменты текста, похожие на интерактивные сеансы Python, а затем выполняет их, чтобы проверить, что они работают именно так, как показано. Существует несколько распространённых способов использовать doctest:
- Проверять, что строки документации модуля актуальны, убеждаясь, что все интерактивные примеры по-прежнему работают в соответствии с документацией.
- Выполнять регрессионное тестирование, проверяя, что интерактивные примеры из тестового файла или тестируемого объекта работают ожидаемым образом.
- Создавать учебную документацию для пакета, щедро иллюстрируя её примерами ввода и вывода. В зависимости от того, что важнее — примеры или пояснительный текст, это напоминает «литературное тестирование» или «исполняемую документацию».
Вот полный, но небольшой пример модуля:
"""
This is the "example" module.
The example module supplies one function, factorial(). For example,
>>> factorial(5)
120
"""
def factorial(n):
"""Return the factorial of n, an exact integer >= 0.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(30)
265252859812191058636308480000000
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
>>> factorial(30.1)
Traceback (most recent call last):
...
ValueError: n must be exact integer
>>> factorial(30.0)
265252859812191058636308480000000
It must also not be ridiculously large:
>>> factorial(1e100)
Traceback (most recent call last):
...
OverflowError: n too large
"""
import math
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
if __name__ == "__main__":
import doctest
doctest.testmod()
Если запустить example.py непосредственно из командной строки, doctest выполнит свою работу:
$ python example.py $
Вывод отсутствует! Это нормально и означает, что все примеры сработали. Передайте скрипту -v, и doctest выведет подробный отчёт о проверяемых примерах, а в конце — сводку:
$ python example.py -v
Trying:
factorial(5)
Expecting:
120
ok
Trying:
[factorial(n) for n in range(6)]
Expecting:
[1, 1, 2, 6, 24, 120]
ok
И так далее, пока в итоге не появится:
Trying:
factorial(1e100)
Expecting:
Traceback (most recent call last):
...
OverflowError: n too large
ok
2 items passed all tests:
1 test in __main__
6 tests in __main__.factorial
7 tests in 2 items.
7 passed.
Test passed.
$
Это всё, что нужно знать, чтобы начать эффективно использовать doctest! Приступайте. В следующих разделах приводятся все подробности. Обратите внимание: в стандартном наборе тестов Python и библиотеках есть множество примеров doctest. Особенно полезные примеры можно найти в стандартном тестовом файле Lib/test/test_doctest/test_doctest.py.
Добавлено в версии 3.13: Вывод по умолчанию раскрашивается; эту настройку можно изменить с помощью переменных среды.
Простое использование: проверка примеров в строках документации
Самый простой способ начать использовать doctest (но не обязательно тот, которым вы будете пользоваться впоследствии) — завершить каждый модуль M следующим:
if __name__ == "__main__":
import doctest
doctest.testmod()
Затем doctest проверяет строки документации в модуле M.
Если запустить модуль как скрипт, примеры из строк документации будут выполнены и проверены:
python M.py
Ничего не будет выведено, если только какой-либо пример не завершится ошибкой. В этом случае в стандартный поток вывода будут напечатаны примеры, завершившиеся ошибкой, и причины ошибок, а последней строкой вывода будет ***Test Failed*** N failures., где N — число примеров, завершившихся ошибкой.
Запустите его с переключателем -v:
python M.py -v
В стандартный поток вывода будет напечатан подробный отчёт обо всех проверенных примерах, а в конце — различные сводки.
Можно принудительно включить подробный режим, передав verbose=True в testmod(), или отключить его, передав verbose=False. В обоих случаях sys.argv не проверяется функцией testmod() (поэтому передача -v или её отсутствие ни на что не влияет).
Для запуска testmod() также предусмотрена команда быстрого запуска; см. раздел Использование из командной строки.
Дополнительные сведения о testmod() см. в разделе Базовый API.
Простое использование: проверка примеров в текстовом файле
Ещё один простой способ применения doctest — проверка интерактивных примеров в текстовом файле. Для этого можно использовать функцию testfile():
import doctest
doctest.testfile("example.txt")
Этот короткий скрипт выполняет и проверяет все интерактивные примеры Python, содержащиеся в файле example.txt. Содержимое файла рассматривается как одна большая строка документации; файл не обязательно должен содержать программу на Python! Например, возможно, в файле example.txt содержится следующее:
The ``example`` module
======================
Using ``factorial``
-------------------
This is an example text file in reStructuredText format. First import
``factorial`` from the ``example`` module:
>>> from example import factorial
Now use it:
>>> factorial(6)
120
При запуске doctest.testfile("example.txt") обнаруживает ошибку в этой документации:
File "./example.txt", line 14, in example.txt
Failed example:
factorial(6)
Expected:
120
Got:
720
Как и testmod(), функция testfile() ничего не выводит, если только какой-либо пример не завершится ошибкой. Если пример завершится ошибкой, в стандартный поток вывода будут напечатаны примеры, завершившиеся ошибкой, и причины ошибок в том же формате, что и у testmod().
По умолчанию функция testfile() ищет файлы в каталоге вызывающего модуля. Описание необязательных аргументов, позволяющих указать другие места поиска файлов, см. в разделе Базовый API.
Как и в случае с testmod(), подробность вывода функции testfile() можно настроить с помощью переключателя командной строки -v или необязательного именованного аргумента verbose.
Для запуска testfile() также предусмотрена команда быстрого запуска; см. раздел Использование из командной строки.
Дополнительные сведения о testfile() см. в разделе Базовый API.
Использование из командной строки
Модуль doctest можно вызвать из командной строки как скрипт:
python -m doctest [-v] [-o OPTION] [-f] file [file ...]
-
-v, --verbose -
В стандартный поток вывода будет напечатан подробный отчёт обо всех проверенных примерах, а в конце — различные сводки:
python -m doctest -v example.py
Это импортирует
example.pyкак самостоятельный модуль и запускает для негоtestmod(). Обратите внимание: такой запуск может работать неправильно, если файл является частью пакета и импортирует другие подмодули этого пакета.Если имя файла не заканчивается на
.py,doctestпредполагает, что вместо этого его нужно запускать с помощьюtestfile():python -m doctest -v example.txt
-
-o, --option <option> -
Флаги параметров управляют различными аспектами поведения doctest; см. раздел Флаги параметров.
Добавлено в версии 3.4.
-
-f, --fail-fast -
Это краткая форма записи
-o FAIL_FAST.Добавлено в версии 3.4.
Как это работает
В этом разделе подробно рассматривается, как работает doctest: какие строки документации он просматривает, как находит интерактивные примеры, какой контекст выполнения использует, как обрабатывает исключения и как с помощью флагов параметров управлять его поведением. Эта информация необходима для написания примеров doctest; сведения о том, как запускать doctest для этих примеров, приведены в следующих разделах.
Какие строки документации просматриваются?
Поиск выполняется в строке документации модуля, а также во всех строках документации функций, классов и методов. Объекты, импортированные в модуль, не просматриваются.
Кроме того, бывают случаи, когда тесты должны быть частью модуля, но не частью текста справки, а значит, тесты не следует включать в строку документации. Doctest ищет переменную уровня модуля с именем __test__ и использует её для поиска других тестов. Если M.__test__ существует, она должна быть словарём, и каждая запись в нём сопоставляет строковое имя с объектом-функцией, объектом-классом или строкой. Просматриваются строки документации функций и классов, найденных через M.__test__, а строки обрабатываются как строки документации. В выводе ключ K из M.__test__ отображается под именем M.__test__.K.
Например, поместите этот блок кода в начало example.py:
__test__ = {
'numbers': """
>>> factorial(6)
720
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
"""
}
Значение example.__test__["numbers"] будет обработано как строка документации, и все содержащиеся в ней тесты будут выполнены. Важно отметить, что значением может быть функция, объект-класс или модуль; в этом случае doctest рекурсивно просматривает их строки документации, в которых затем ищет тесты.
Все найденные классы аналогичным образом просматриваются рекурсивно, чтобы проверить строки документации содержащихся в них методов и вложенных классов.
Примечание
doctest может автоматически обнаруживать только классы и функции, определённые на уровне модуля или внутри других классов.
Вложенные классы и функции существуют только после вызова внешней функции, поэтому обнаружить их невозможно. Определяйте их вне внешней функции, чтобы они были видны.
Как распознаются примеры в строках документации?
В большинстве случаев копирование интерактивного сеанса консоли с последующей вставкой работает без проблем, но doctest не пытается точно эмулировать какую-либо конкретную оболочку Python.
>>> # comments are ignored
>>> x = 12
>>> x
12
>>> if x == 13:
... print("yes")
... else:
... print("no")
... print("NO")
... print("NO!!!")
...
no
NO
NO!!!
>>>
Ожидаемый вывод должен непосредственно следовать за последней строкой '>>> ' или '... ', содержащей код; ожидаемый вывод (если он есть) продолжается до следующей строки '>>> ' или строки, состоящей только из пробельных символов.
Подробности:
- Ожидаемый вывод не может содержать строку, состоящую только из пробельных символов, поскольку такая строка обозначает конец ожидаемого вывода. Если ожидаемый вывод содержит пустую строку, укажите
<BLANKLINE>в примере doctest в каждом месте, где ожидается пустая строка. - Все символы табуляции преобразуются в пробелы с шагом табуляции в 8 столбцов. Табуляции в выводе проверяемого кода не изменяются. Поскольку все символы табуляции в образце вывода преобразуются, если вывод кода содержит символы табуляции, doctest может пройти только при включённом параметре
NORMALIZE_WHITESPACEили директиве. В качестве альтернативы тест можно переписать так, чтобы он перехватывал вывод и сравнивал его с ожидаемым значением. Такой способ обработки табуляций в исходном тексте был выбран методом проб и ошибок и оказался наименее подверженным ошибкам. Для обработки табуляций можно использовать другой алгоритм, написав собственный классDocTestParser. - Вывод в stdout перехватывается, но вывод в stderr — нет (трассировки исключений перехватываются другим способом).
-
Если в интерактивном сеансе вы продолжаете строку с помощью обратной косой черты или используете её по какой-либо другой причине, следует использовать необработанную строку документации, которая сохранит обратные косые черты именно в том виде, в каком вы их ввели:
>>> def f(x): ... r'''Backslashes in a raw docstring: m\n''' ... >>> print(f.__doc__) Backslashes in a raw docstring: m\n
В противном случае обратная косая черта будет интерпретирована как часть строки. Например, приведённая выше
\nбудет интерпретирована как символ новой строки. В качестве альтернативы можно удвоить каждую обратную косую черту в версии doctest (и не использовать необработанную строку):>>> def f(x): ... '''Backslashes in a raw docstring: m\\n''' ... >>> print(f.__doc__) Backslashes in a raw docstring: m\n
-
Начальный столбец не имеет значения:
>>> assert "Easy!" >>> import math >>> math.floor(1.9) 1из ожидаемого вывода удаляется столько начальных пробельных символов, сколько было в исходной строке
'>>> ', с которой начинался пример.
Каков контекст выполнения?
По умолчанию каждый раз, когда doctest находит строку документации для проверки, он использует поверхностную копию глобальных переменных M, чтобы запуск тестов не изменял настоящие глобальные переменные модуля и чтобы один тест в M не оставлял после себя следов, которые случайно позволили бы пройти другому тесту. Это означает, что в примерах можно свободно использовать любые имена, определённые на верхнем уровне в M, а также имена, определённые ранее в проверяемой строке документации. Примеры не видят имена, определённые в других строках документации.
Можно принудительно использовать собственный словарь в качестве контекста выполнения, передав globs=your_dict вместо этого функциям testmod() или testfile().
А как насчёт исключений?
Проблем не возникнет, если трассировка стека — единственный вывод примера: просто вставьте её. [1] Поскольку трассировки стека содержат сведения, которые могут быстро меняться (например, точные пути к файлам и номера строк), в этом случае doctest стремится быть гибким в том, что он принимает.
Простой пример:
>>> [1, 2, 3].remove(42) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.remove(x): x not in list
Этот тест doctest пройдёт, если будет вызвано исключение ValueError с указанной детализацией list.remove(x):
x not in list.
Ожидаемый вывод исключения должен начинаться с заголовка трассировки стека, которым может быть одна из следующих двух строк с отступом, совпадающим с отступом первой строки примера:
Traceback (most recent call last): Traceback (innermost last):
За заголовком трассировки стека может следовать необязательный стек вызовов, содержимое которого doctest игнорирует. Стек вызовов обычно опускают или копируют дословно из интерактивного сеанса.
За стеком вызовов следует самая важная часть: строка или строки с типом и описанием исключения. Обычно это последняя строка трассировки стека, но она может занимать несколько строк, если описание исключения состоит из нескольких строк:
>>> raise ValueError('multi\n line\ndetail')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: multi
line
detail
Последние три строки (начиная с ValueError) сравниваются с типом и описанием исключения, а остальные игнорируются.
Рекомендуется опускать стек вызовов, если только он не добавляет примеру существенной ценности с точки зрения документации. Поэтому последний пример, вероятно, лучше записать так:
>>> raise ValueError('multi\n line\ndetail')
Traceback (most recent call last):
...
ValueError: multi
line
detail
Обратите внимание, что трассировки стека обрабатываются особым образом. В частности, в переписанном примере использование ... не зависит от параметра doctest ELLIPSIS. Многоточие в этом примере можно опустить или заменить тремя (или тремястами) запятыми или цифрами либо строкой с отступом из скетча «Летающего цирка Монти Пайтона».
Некоторые подробности достаточно прочитать один раз — запоминать их не нужно:
- Doctest не может определить, получен ли ожидаемый вывод из трассировки исключения или в результате обычной печати. Поэтому, например, пример, ожидающий
ValueError: 42 is prime, пройдёт независимо от того, было ли действительно вызвано исключениеValueErrorили пример просто вывел этот текст трассировки. На практике обычный вывод редко начинается со строки заголовка трассировки, поэтому это не создаёт реальных проблем. - Каждая строка стека вызовов (если он есть) должна иметь больший отступ, чем первая строка примера, либо начинаться с не буквенно-цифрового символа. Первая строка после заголовка трассировки, имеющая такой же отступ и начинающаяся с буквенно-цифрового символа, считается началом описания исключения. Для настоящих трассировок это работает правильно.
- Если указан параметр doctest
IGNORE_EXCEPTION_DETAIL, всё после первого двоеточия, а также сведения о модуле в имени исключения игнорируются. - Для некоторых исключений
SyntaxErrorинтерактивная оболочка опускает строку заголовка трассировки. Но doctest использует её, чтобы отличать исключения от ситуаций, когда исключения нет. Поэтому в редком случае, когда нужно проверитьSyntaxErrorбез заголовка трассировки, заголовок трассировки придётся вручную добавить в пример теста.
-
Для некоторых исключений Python показывает положение ошибки с помощью маркеров
^и тильд:>>> 1 + None File "<stdin>", line 1 1 + None ~~^~~~~~ TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'Поскольку строки, указывающие положение ошибки, идут перед типом и описанием исключения, doctest их не проверяет. Например, следующий тест пройдёт, даже если маркер
^расположен неверно:>>> 1 + None File "<stdin>", line 1 1 + None ^~~~~~~~ TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
Флаги параметров
Поведение doctest в разных аспектах регулируется рядом флагов параметров. Символические имена флагов заданы как константы модуля; их можно объединять с помощью операции побитового ИЛИ и передавать различным функциям. Эти имена также можно использовать в директивах doctest и передавать интерфейсу командной строки doctest с помощью параметра -o.
Первая группа параметров задаёт семантику тестов, управляя тем, как doctest определяет, соответствует ли фактический вывод ожидаемому выводу примера:
-
doctest.DONT_ACCEPT_TRUE_FOR_1 -
По умолчанию, если блок ожидаемого вывода содержит только
1, фактический блок вывода, содержащий только1или толькоTrue, считается совпадением; аналогично для0иFalse. Если указан параметрDONT_ACCEPT_TRUE_FOR_1, обе подстановки запрещены. Такое поведение по умолчанию учитывает, что в Python тип возвращаемого значения многих функций изменился с целого числа на логическое значение; doctest, ожидающие в таких случаях вывод «небольшого целого числа», продолжат работать. Вероятно, этот параметр будет удалён, но не раньше чем через несколько лет.
-
doctest.DONT_ACCEPT_BLANKLINE -
По умолчанию, если блок ожидаемого вывода содержит строку, состоящую только из строки
<BLANKLINE>, эта строка соответствует пустой строке в фактическом выводе. Поскольку настоящая пустая строка обозначает конец ожидаемого вывода, это единственный способ указать, что ожидается пустая строка. Если указан параметрDONT_ACCEPT_BLANKLINE, такая подстановка запрещена.
-
doctest.NORMALIZE_WHITESPACE -
Если параметр указан, любые последовательности пробельных символов (пробелов и символов новой строки) считаются равными. Любая последовательность пробельных символов в ожидаемом выводе соответствует любой последовательности пробельных символов в фактическом выводе. По умолчанию пробельные символы должны совпадать точно. Параметр
NORMALIZE_WHITESPACEособенно полезен, когда строка ожидаемого вывода очень длинная и её нужно перенести на несколько строк в исходном коде.
-
doctest.ELLIPSIS -
Если параметр указан, маркер многоточия (
...) в ожидаемом выводе может соответствовать любой подстроке фактического вывода. Это включает подстроки, пересекающие границы строк, и пустые подстроки, поэтому маркер лучше использовать просто. Сложное использование может привести к тем же неожиданностям в духе «ой, совпало слишком многое!», к которым склонен.*в регулярных выражениях.
-
doctest.IGNORE_EXCEPTION_DETAIL -
Если параметр указан, тесты doctest, ожидающие исключения, проходят, если возникает исключение ожидаемого типа, даже когда его подробности (сообщение и полное имя исключения) не совпадают.
Например, пример, ожидающий
ValueError: 42, пройдёт, если фактически возникшее исключение —ValueError: 3*14, но не пройдёт, если вместо него возникнет, например,TypeError. Также будет игнорироваться полное имя, указанное перед классом исключения, поскольку оно может различаться в разных реализациях и версиях Python, а также в используемом коде и библиотеках. Поэтому при включённом флаге будут работать все три следующих варианта:>>> raise Exception('message') Traceback (most recent call last): Exception: message >>> raise Exception('message') Traceback (most recent call last): builtins.Exception: message >>> raise Exception('message') Traceback (most recent call last): __main__.Exception: messageОбратите внимание, что
ELLIPSISтакже можно использовать, чтобы игнорировать подробности сообщения исключения, однако такой тест всё ещё может завершиться неудачей, если имя модуля отсутствует или не совпадает в точности.Изменено в версии 3.2:
IGNORE_EXCEPTION_DETAILтеперь также игнорирует любые сведения о модуле, содержащем проверяемое исключение.
-
doctest.SKIP -
Если параметр указан, пример вообще не запускается. Это может быть полезно в случаях, когда примеры doctest служат одновременно документацией и тестами: пример нужно включить в документацию, но проверять его не следует. Например, вывод примера может быть случайным или он может зависеть от ресурсов, недоступных тестовому драйверу.
Флаг SKIP также можно использовать для временного «комментирования» примеров.
-
doctest.COMPARISON_FLAGS -
Битовая маска, объединяющая все перечисленные выше флаги сравнения.
Вторая группа параметров управляет тем, как сообщается о неудачных тестах:
-
doctest.REPORT_UDIFF -
Если параметр указан, различия между многострочными ожидаемым и фактическим выводами отображаются в виде унифицированного diff.
-
doctest.REPORT_CDIFF -
Если параметр указан, различия между многострочными ожидаемым и фактическим выводами отображаются в виде контекстного diff.
-
doctest.REPORT_NDIFF -
Если параметр указан, различия вычисляются с помощью
difflib.Differ, использующего тот же алгоритм, что и популярная утилитаndiff.py. Это единственный метод, отмечающий различия как между строками, так и внутри них. Например, если в ожидаемом выводе в строке стоит цифра1, а в фактическом выводе — букваl, вставляется строка с символом-указателем, отмечающим позиции несовпадающих столбцов.
-
doctest.REPORT_ONLY_FIRST_FAILURE -
Если параметр указан, отображается первый неудачный пример в каждом тесте doctest, а вывод для всех остальных примеров подавляется. Это не позволит doctest сообщать о корректных примерах, которые завершаются с ошибкой из-за предыдущих неудач, но может скрыть и некорректные примеры, не связанные с первой ошибкой. Если указан параметр
REPORT_ONLY_FIRST_FAILURE, остальные примеры всё равно запускаются и учитываются в общем числе обнаруженных ошибок; подавляется только вывод.
-
doctest.FAIL_FAST -
Если параметр указан, выполнение прекращается после первого неудачного примера, а остальные примеры не запускаются. Таким образом, будет сообщено не более чем об одной ошибке. Этот флаг может быть полезен при отладке, поскольку примеры после первой ошибки даже не выведут отладочную информацию.
-
doctest.REPORTING_FLAGS -
Битовая маска, объединяющая все перечисленные выше флаги отчётности.
Можно также зарегистрировать новые имена флагов параметров, хотя это полезно только в том случае, если вы собираетесь расширять внутренние механизмы doctest с помощью наследования:
-
doctest.register_optionflag(name) -
Создаёт флаг параметра с заданным именем и возвращает его целочисленное значение.
register_optionflag()можно использовать при наследовании отOutputCheckerилиDocTestRunner, чтобы создавать параметры, поддерживаемые вашими подклассами.register_optionflag()всегда следует вызывать следующим образом:MY_FLAG = register_optionflag('MY_FLAG')
Директивы
Директивы doctest позволяют изменить флаги параметров для отдельного примера. Директивы doctest — это специальные комментарии Python, следующие за исходным кодом примера:
directive: "#" "doctest:" directive_options directive_options: directive_option ("," directive_option)* directive_option: on_or_off directive_option_name on_or_off: "+" | "-" directive_option_name: "DONT_ACCEPT_BLANKLINE" | "NORMALIZE_WHITESPACE" | ...
Между + или - и именем параметра директивы не должно быть пробелов. Именем параметра директивы может быть любое из описанных выше имён флагов.
Директивы doctest в примере изменяют поведение doctest только для этого примера. Используйте +, чтобы включить указанный режим, или -, чтобы отключить его.
Например, этот тест проходит:
>>> print(list(range(20))) # doctest: +NORMALIZE_WHITESPACE [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
Без директивы тест завершился бы неудачей: в фактическом выводе нет двух пробелов перед элементами списка из одной цифры, а сам вывод занимает одну строку. Этот тест также проходит, и для этого тоже требуется директива:
>>> print(list(range(20))) # doctest: +ELLIPSIS [0, 1, ..., 18, 19]
В одной физической строке можно использовать несколько директив, разделив их запятыми:
>>> print(list(range(20))) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE [0, 1, ..., 18, 19]
Если для одного примера используются несколько комментариев с директивами, они объединяются:
>>> print(list(range(20))) # doctest: +ELLIPSIS ... # doctest: +NORMALIZE_WHITESPACE [0, 1, ..., 18, 19]
Как показывает предыдущий пример, в примере можно добавить строки ..., содержащие только директивы. Это может быть полезно, если пример слишком длинный и директиву неудобно помещать в ту же строку:
>>> print(list(range(5)) + list(range(10, 20)) + list(range(30, 40))) ... # doctest: +ELLIPSIS [0, ..., 4, 10, ..., 19, 30, ..., 39]
Поскольку по умолчанию все параметры отключены, а директивы применяются только к тому примеру, в котором они указаны, обычно имеет смысл только включать параметры (с помощью + в директиве). Однако флаги параметров также можно передавать функциям, запускающим тесты doctest, тем самым задавая другие значения по умолчанию. В таких случаях бывает полезно отключить параметр с помощью - в директиве.
Предупреждения
doctest требует точного совпадения ожидаемого вывода. Если не совпадает хотя бы один символ, тест завершается неудачей. Это, вероятно, несколько раз вас удивит, пока вы будете разбираться, что именно Python гарантирует и не гарантирует в выводе. Например, при печати множества Python не гарантирует, что его элементы будут выведены в определённом порядке, поэтому такой тест
>>> foo()
{"spam", "eggs"}
ненадёжен! Один из способов обойти проблему — сделать так:
>>> foo() == {"spam", "eggs"}
True
Другой способ — сделать так:
>>> d = sorted(foo()) >>> d ['eggs', 'spam']
Есть и другие варианты, но суть вы поняли.
Ещё одна плохая идея — печатать значения, содержащие адрес объекта, например:
>>> id(1.0) # certain to fail some of the time 7948648 >>> class C: pass >>> C() # the default repr() for instances embeds an address <C object at 0x00AC18F0>
Директива ELLIPSIS удобно решает проблему в последнем примере:
>>> C() # doctest: +ELLIPSIS <C object at 0x...>
Числа с плавающей точкой также могут немного различаться при выводе на разных платформах, поскольку Python использует системную библиотеку C для некоторых вычислений с плавающей точкой, а качество библиотек C заметно различается.
>>> 1000**0.1 # risky
1.9952623149688797
>>> round(1000**0.1, 9) # safer
1.995262315
>>> print(f'{1000**0.1:.4f}') # much safer
1.9953
Числа вида I/2.**J надёжны на всех платформах, и я часто специально составляю примеры doctest так, чтобы получать числа именно такого вида:
>>> 3./4 # utterly safe 0.75
Простые дроби также легче понять, а значит, документация получается лучше.
Базовый API
Функции testmod() и testfile() предоставляют простой интерфейс к doctest, которого должно быть достаточно для большинства базовых задач. Более подробное введение в эти две функции см. в разделах Простое использование: проверка примеров в строках документации и Простое использование: проверка примеров в текстовом файле.
-
doctest.testfile(filename, module_relative=True, name=None, package=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, parser=DocTestParser(), encoding=None) -
Все аргументы, кроме filename, необязательны и должны задаваться в виде именованных аргументов.
Проверяет примеры в файле с именем filename. Возвращает
(failure_count, test_count).Необязательный аргумент module_relative определяет, как следует интерпретировать имя файла:
- Если module_relative равен
True(значение по умолчанию), то filename задаёт независимый от ОС путь относительно модуля. По умолчанию этот путь отсчитывается от каталога вызывающего модуля; если же указан аргумент package, путь отсчитывается от этого пакета. Чтобы обеспечить независимость от ОС, для разделения сегментов пути в filename следует использовать символы/, и он не может быть абсолютным путём (то есть не может начинаться с/). - Если module_relative равен
False, то filename задаёт зависящий от ОС путь. Путь может быть абсолютным или относительным; относительные пути разрешаются относительно текущего рабочего каталога.
Необязательный аргумент name задаёт имя теста; по умолчанию или если
Noneиспользуетсяos.path.basename(filename).Необязательный аргумент package — это пакет Python или имя пакета Python, каталог которого следует использовать в качестве базового каталога для пути относительно модуля. Если пакет не указан, в качестве базового каталога для путей относительно модуля используется каталог вызывающего модуля. Указывать package, если module_relative равен
False, нельзя.Необязательный аргумент globs задаёт словарь, который будет использоваться как глобальное пространство имён при выполнении примеров. Для doctest создаётся новая поверхностная копия этого словаря, поэтому примеры начинают работу с чистого состояния. По умолчанию или если
Noneиспользуется новый пустой словарь.Необязательный аргумент extraglobs задаёт словарь, объединяемый с глобальным пространством имён, используемым для выполнения примеров. Это работает так же, как
dict.update(): если в globs и extraglobs есть общий ключ, в объединённый словарь попадает соответствующее значение из extraglobs. По умолчанию или еслиNoneдополнительные глобальные переменные не используются. Это расширенная функция, позволяющая параметризовать doctest. Например, можно написать doctest для базового класса, используя общее имя класса, а затем повторно использовать его для проверки любого количества подклассов, передав словарь extraglobs, в котором общее имя сопоставлено с проверяемым подклассом.Необязательный аргумент verbose при значении true выводит много информации, а при значении false — только сведения о сбоях; по умолчанию или если
Noneон имеет значение true тогда и только тогда, когда'-v'присутствует вsys.argv.Необязательный аргумент report при значении true выводит сводку в конце, а в противном случае ничего не выводит. В подробном режиме сводка содержит много информации, в противном случае она очень краткая (фактически пустая, если все тесты пройдены).
Необязательный аргумент optionflags (значение по умолчанию
0) принимает результат побитового ИЛИ флагов параметров. См. раздел Флаги параметров.Необязательный аргумент raise_on_error по умолчанию имеет значение false. Если он имеет значение true, при первом сбое или неожиданном исключении в примере возникает исключение. Это позволяет выполнять посмертную отладку сбоев. По умолчанию выполнение примеров продолжается.
Необязательный аргумент parser задаёт
DocTestParser(или подкласс), который следует использовать для извлечения тестов из файлов. По умолчанию используется обычный анализатор (то естьDocTestParser()).Необязательный аргумент encoding задаёт кодировку, которую следует использовать для преобразования файла в Unicode.
- Если module_relative равен
-
doctest.testmod(m=None, name=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False) -
Все аргументы необязательны, и все, кроме m, должны задаваться в виде именованных аргументов.
Проверяет примеры в строках документации функций и классов, доступных из модуля m (или модуля
__main__, если m не указан или равенNone), начиная сm.__doc__.Также проверяет примеры, доступные из словаря
m.__test__, если он существует.m.__test__сопоставляет имена (строки) функциям, классам и строкам; строки документации функций и классов проверяются на наличие примеров, а строки проверяются непосредственно, как если бы они были строками документации.Проверяются только строки документации объектов, принадлежащих модулю m.
Возвращает
(failure_count, test_count).Необязательный аргумент name задаёт имя модуля; по умолчанию или если
Noneиспользуетсяm.__name__.Необязательный аргумент exclude_empty по умолчанию имеет значение false. Если он имеет значение true, объекты, для которых не найдено ни одного doctest, исключаются из проверки. Значение по умолчанию — это обходной приём для обратной совместимости: код, который по-прежнему использует
doctest.master.summarizeвместе сtestmod(), продолжает выводить сведения об объектах, для которых нет тестов. Аргумент exclude_empty нового конструктораDocTestFinderпо умолчанию имеет значение true.Необязательные аргументы extraglobs, verbose, report, optionflags, raise_on_error и globs работают так же, как и для описанной выше функции
testfile(), за исключением того, что для globs значением по умолчанию являетсяm.__dict__.
-
doctest.run_docstring_examples(f, globs, verbose=False, name='NoName', compileflags=None, optionflags=0) -
Проверяет примеры, связанные с объектом f; например, f может быть строкой, модулем, функцией или объектом класса.
Для контекста выполнения используется поверхностная копия словаря globs.
Необязательный аргумент name используется в сообщениях о сбоях; по умолчанию он равен
"NoName".Если необязательный аргумент verbose имеет значение true, вывод создаётся, даже если сбоев нет. По умолчанию вывод создаётся только в случае сбоя примера.
Необязательный аргумент compileflags задаёт набор флагов, которые компилятор Python должен использовать при выполнении примеров. По умолчанию или если
Noneфлаги определяются на основе набора будущих возможностей, найденных в globs.Необязательный аргумент optionflags работает так же, как и для описанной выше функции
testfile().
API unittest
По мере роста вашей коллекции модулей с doctest вам понадобится способ систематически запускать все их doctest. doctest предоставляет две функции, которые можно использовать для создания наборов тестов unittest из модулей и текстовых файлов, содержащих doctest. Чтобы интегрировать их с механизмом обнаружения тестов unittest, включите в свой тестовый модуль функцию load_tests:
import unittest
import doctest
import my_module_with_doctests
def load_tests(loader, tests, ignore):
tests.addTests(doctest.DocTestSuite(my_module_with_doctests))
return tests
Существует две основные функции для создания экземпляров unittest.TestSuite из текстовых файлов и модулей, содержащих doctest:
-
doctest.DocFileSuite(*paths, module_relative=True, package=None, setUp=None, tearDown=None, globs=None, optionflags=0, parser=DocTestParser(), encoding=None) -
Преобразует doctest из одного или нескольких текстовых файлов в
unittest.TestSuite.Возвращённый объект
unittest.TestSuiteпредназначен для запуска средствами unittest и выполняет интерактивные примеры из каждого файла. Если пример в каком-либо файле завершается с ошибкой, синтезированный модульный тест также завершается с ошибкой, и возникает исключениеfailureException, в котором указаны имя файла с тестом и (иногда приблизительный) номер строки. Если все примеры в файле пропущены, синтезированный модульный тест также помечается как пропущенный.Передайте один или несколько путей (в виде строк) к текстовым файлам для проверки.
Параметры можно передавать в виде именованных аргументов:
Необязательный аргумент module_relative определяет, как следует интерпретировать имена файлов в paths:
- Если module_relative равен
True(значение по умолчанию), каждое имя файла в paths задаёт независимый от ОС путь относительно модуля. По умолчанию этот путь отсчитывается от каталога вызывающего модуля; если же указан аргумент package, путь отсчитывается от этого пакета. Чтобы обеспечить независимость от ОС, для разделения сегментов пути в каждом имени файла следует использовать символы/, и имена не могут быть абсолютными путями (то есть не могут начинаться с/). - Если module_relative равен
False, каждое имя файла в paths задаёт зависящий от ОС путь. Путь может быть абсолютным или относительным; относительные пути разрешаются относительно текущего рабочего каталога.
Необязательный аргумент package — это пакет Python или имя пакета Python, каталог которого следует использовать в качестве базового каталога для путей относительно модуля в paths. Если пакет не указан, в качестве базового каталога для путей относительно модуля используется каталог вызывающего модуля. Указывать package, если module_relative равен
False, нельзя.Необязательный аргумент setUp задаёт функцию подготовки набора тестов. Она вызывается перед запуском тестов в каждом файле. Функции setUp передаётся объект
DocTest. Функция setUp может получить доступ к глобальным переменным теста через атрибутglobsпереданного теста.Необязательный аргумент tearDown задаёт функцию очистки набора тестов. Она вызывается после запуска тестов в каждом файле. Функции tearDown передаётся объект
DocTest. Функция tearDown может получить доступ к глобальным переменным теста через атрибутglobsпереданного теста.Необязательный аргумент globs — это словарь, содержащий начальные глобальные переменные для тестов. Для каждого теста создаётся новая копия этого словаря. По умолчанию globs — это новый пустой словарь.
Необязательный аргумент optionflags задаёт параметры doctest по умолчанию для тестов; они создаются объединением отдельных флагов параметров с помощью операции ИЛИ. См. раздел Флаги параметров. О более удобном способе настройки параметров отчётов см. описанную ниже функцию
set_unittest_reportflags().Необязательный аргумент parser задаёт
DocTestParser(или подкласс), который следует использовать для извлечения тестов из файлов. По умолчанию используется обычный анализатор (то естьDocTestParser()).Необязательный аргумент encoding задаёт кодировку, которую следует использовать для преобразования файла в Unicode.
Глобальная переменная
__file__добавляется к глобальным переменным, предоставляемым doctest, загруженным из текстового файла с помощьюDocFileSuite(). - Если module_relative равен
-
doctest.DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, setUp=None, tearDown=None, optionflags=0, checker=None) -
Преобразует doctest модуля в
unittest.TestSuite.Возвращённый объект
unittest.TestSuiteпредназначен для запуска средствами unittest и выполняет каждый doctest модуля. Каждая строка документации выполняется как отдельный модульный тест. Если какой-либо doctest завершается с ошибкой, синтезированный модульный тест также завершается с ошибкой, и возникает исключениеunittest.TestCase.failureException, в котором указаны имя файла с тестом и (иногда приблизительный) номер строки. Если все примеры в строке документации пропущены, тоНеобязательный аргумент module задаёт модуль для проверки. Это может быть объект модуля или имя модуля (возможно, составное). Если аргумент не указан, используется модуль, вызвавший эту функцию.
Необязательный аргумент globs — это словарь, содержащий начальные глобальные переменные для тестов. Для каждого теста создаётся новая копия этого словаря. По умолчанию globs — это
__dict__модуля.Необязательный аргумент extraglobs задаёт дополнительный набор глобальных переменных, который объединяется с globs. По умолчанию дополнительные глобальные переменные не используются.
Необязательный аргумент test_finder — это объект
DocTestFinder(или его полная замена), используемый для извлечения doctest из модуля.Необязательные аргументы setUp, tearDown и optionflags работают так же, как и для описанной выше функции
DocFileSuite(), но вызываются для каждой строки документации.Эта функция использует тот же способ поиска, что и
testmod().Изменено в версии 3.5:
DocTestSuite()возвращает пустой объектunittest.TestSuite, если module не содержит строк документации, вместо того чтобы вызыватьValueError.
Внутри DocTestSuite() создаёт объект unittest.TestSuite из экземпляров doctest.DocTestCase, а DocTestCase является подклассом unittest.TestCase. DocTestCase здесь не документирован (это внутренняя деталь), однако изучение его кода может помочь разобраться в точных деталях интеграции с unittest.
Аналогично, DocFileSuite() создаёт объект unittest.TestSuite из экземпляров doctest.DocFileCase, а DocFileCase является подклассом DocTestCase.
Таким образом, оба способа создания unittest.TestSuite запускают экземпляры DocTestCase. Это важно по одной неочевидной причине: при самостоятельном вызове функций doctest можно напрямую управлять используемыми параметрами doctest, передавая флаги параметров функциям doctest. Однако если вы пишете платформу unittest, то в конечном счёте именно unittest управляет тем, когда и как запускаются тесты. Автор платформы обычно хочет управлять параметрами отчётов doctest (например, заданными параметрами командной строки), но передать параметры через unittest исполнителям тестов doctest невозможно.
По этой причине doctest также поддерживает концепцию флагов отчётов doctest, предназначенных для работы с unittest, с помощью этой функции:
-
doctest.set_unittest_reportflags(flags) -
Задаёт флаги отчётов
doctest.Аргумент flags принимает результат побитового ИЛИ флагов параметров. См. раздел Флаги параметров. Можно использовать только «флаги отчётов».
Это глобальный параметр модуля, влияющий на все последующие doctest, запускаемые модулем
unittest: методrunTest()классаDocTestCaseпроверяет флаги параметров, указанные для тестового случая при создании экземпляраDocTestCase. Если флаги отчётов не были указаны (что является типичным и ожидаемым случаем), флаги отчётовdoctestunittestобъединяются побитовой операцией ИЛИ с флагами параметров, и дополненные таким образом флаги передаются экземпляруDocTestRunner, созданному для запуска doctest. Если при создании экземпляраDocTestCaseбыли указаны какие-либо флаги отчётов, флаги отчётовdoctestunittestигнорируются.Функция возвращает значение флагов отчётов
unittest, действовавшее до её вызова.
Расширенный API
Базовый API — это простая оболочка, предназначенная для упрощения использования doctest. Она достаточно гибкая и должна удовлетворять потребности большинства пользователей; однако, если вам требуется более тонкий контроль над тестированием или вы хотите расширить возможности doctest, следует использовать расширенный API.
В основе расширенного API лежат два класса-контейнера, используемые для хранения интерактивных примеров, извлечённых из тестовых примеров doctest:
-
Example: отдельная инструкция Python инструкция в паре с ожидаемым результатом. -
DocTest: коллекция объектовExample, обычно извлечённых из одной строки документации или текстового файла.
Для поиска, разбора, запуска и проверки примеров doctest определены дополнительные классы обработки:
-
DocTestFinder: находит все строки документации в указанном модуле и используетDocTestParser, чтобы создать объектDocTestдля каждой строки документации, содержащей интерактивные примеры. -
DocTestParser: создаёт объектDocTestиз строки (например, из строки документации объекта). -
DocTestRunner: выполняет примеры из объектаDocTestи используетOutputCheckerдля проверки их вывода. -
OutputChecker: сравнивает фактический вывод примера doctest с ожидаемым и определяет, совпадают ли они.
Взаимосвязи между этими классами обработки представлены на следующей диаграмме:
list of:
+------+ +---------+
|module| --DocTestFinder-> | DocTest | --DocTestRunner-> results
+------+ | ^ +---------+ | ^ (printed)
| | | Example | | |
v | | ... | v |
DocTestParser | Example | OutputChecker
+---------+
Объекты DocTest
-
class doctest.DocTest(examples, globs, name, filename, lineno, docstring) -
Коллекция примеров doctest, которые следует выполнить в одном пространстве имён. Аргументы конструктора используются для инициализации атрибутов с такими же именами.
DocTestопределяет следующие атрибуты. Они инициализируются конструктором, и их не следует изменять напрямую.-
examples -
Список объектов
Example, представляющих отдельные интерактивные примеры Python, которые должен выполнить этот тест.
-
globs -
Пространство имён (то есть глобальные переменные), в котором следует выполнять примеры. Это словарь, сопоставляющий имена значениям. Любые изменения пространства имён, внесённые примерами (например, привязка новых переменных), будут отражены в
globsпосле выполнения теста.
-
name -
Строковое имя, идентифицирующее
DocTest. Обычно это имя объекта или файла, из которого был извлечён тест.
-
filename -
Имя файла, из которого был извлечён этот
DocTest; илиNone, если имя файла неизвестно либо еслиDocTestбыл извлечён не из файла.
-
lineno
-
docstring -
Строка, из которой был извлечён тест, или
None, если строка недоступна либо если тест был извлечён не из строки.
-
Объекты Example
-
class doctest.Example(source, want, exc_msg=None, lineno=0, indent=0, options=None) -
Отдельный интерактивный пример, состоящий из инструкции Python и ожидаемого результата. Аргументы конструктора используются для инициализации атрибутов с такими же именами.
Exampleопределяет следующие атрибуты. Они инициализируются конструктором, и их не следует изменять напрямую.-
source -
Строка с исходным кодом примера. Этот исходный код состоит из одной инструкции Python и всегда заканчивается символом новой строки; при необходимости конструктор добавляет символ новой строки.
-
want -
Ожидаемый вывод при выполнении исходного кода примера (из стандартного вывода или трассировка стека в случае исключения).
wantзаканчивается символом новой строки, если только вывод не ожидается — в этом случае это пустая строка. При необходимости конструктор добавляет символ новой строки.
-
exc_msg -
Сообщение об исключении, сформированное примером, если ожидается, что пример вызовет исключение; или
None, если исключение не ожидается. Это сообщение об исключении сравнивается с возвращаемым значениемtraceback.format_exception_only().exc_msgзаканчивается символом новой строки, если только это неNone. При необходимости конструктор добавляет символ новой строки.
-
lineno -
Номер строки в содержащей пример строке, с которой начинается пример. Нумерация строк начинается с нуля от начала содержащей его строки.
-
indent -
Отступ примера в содержащей его строке, то есть количество пробелов перед первым приглашением ввода примера.
-
options -
Словарь, сопоставляющий флаги параметров значениям
TrueилиFalse; он используется для переопределения параметров по умолчанию для этого примера. Флаги параметров, отсутствующие в этом словаре, сохраняют значения по умолчанию (заданные параметромDocTestRunneroptionflags). По умолчанию параметры не заданы.
-
Объекты DocTestFinder
-
class doctest.DocTestFinder(verbose=False, parser=DocTestParser(), recurse=True, exclude_empty=True) -
Класс обработки, используемый для извлечения из строки документации заданного объекта и строк документации содержащихся в нём объектов объектов
DocTest, относящихся к этому объекту. ОбъектыDocTestможно извлекать из модулей, классов, функций, методов, статических методов, методов класса и свойств.Необязательный аргумент verbose позволяет отображать объекты, проверяемые средством поиска. По умолчанию он равен
False(вывод отсутствует).Необязательный аргумент parser задаёт объект
DocTestParser(или его полную замену), используемый для извлечения doctest из строк документации.Если необязательный аргумент recurse имеет значение false, то
DocTestFinder.find()проверит только заданный объект, не проверяя содержащиеся в нём объекты.Если необязательный аргумент exclude_empty имеет значение false, то
DocTestFinder.find()включит тесты для объектов с пустыми строками документации.DocTestFinderопределяет следующий метод:-
find(obj[, name][, module][, globs][, extraglobs]) -
Возвращает список объектов
DocTest, определённых строкой документации obj или строками документации любых содержащихся в нём объектов.Необязательный аргумент name задаёт имя объекта; это имя будет использоваться при построении имён возвращаемых объектов
DocTest. Если name не задан, используетсяobj.__name__.Необязательный параметр module — это модуль, содержащий заданный объект. Если модуль не задан или равен
None, средство поиска тестов попытается автоматически определить нужный модуль. Модуль объекта используется:- Как пространство имён по умолчанию, если globs не задан.
- Для предотвращения извлечения DocTestFinder объектов DocTest из объектов, импортированных из других модулей. (Содержащиеся объекты, модуль которых отличается от module, игнорируются.)
- Для определения имени файла, содержащего объект.
- Для определения номера строки объекта в его файле.
Если module равен
False, попытка найти модуль предприниматься не будет. Это редко используемый параметр, полезный главным образом при тестировании самого doctest: если module равенFalseили равенNone, но не может быть найден автоматически, то все объекты считаются принадлежащими несуществующему модулю, поэтому все содержащиеся объекты будут рекурсивно проверены на наличие doctest.Глобальные переменные для каждого объекта
DocTestформируются путём объединения globs и extraglobs (привязки в extraglobs имеют приоритет над привязками в globs). Для каждогоDocTestсоздаётся новая поверхностная копия словаря глобальных переменных. Если globs не задан, по умолчанию используется__dict__модуля, если он задан, либо{}. Если extraglobs не задан, по умолчанию используется{}.
-
Объекты DocTestParser
-
class doctest.DocTestParser -
Класс обработки, используемый для извлечения интерактивных примеров из строки и создания на их основе объекта
DocTest.DocTestParserопределяет следующие методы:-
get_doctest(string, globs, name, filename, lineno) -
Извлекает все примеры doctest из заданной строки и собирает их в объект
DocTest.globs, name, filename и lineno — это атрибуты нового объекта
DocTest. Дополнительные сведения см. в документации дляDocTest.
-
get_examples(string, name='<string>') -
Извлекает все примеры doctest из заданной строки и возвращает их в виде списка объектов
Example. Нумерация строк начинается с нуля. Необязательный аргумент name — это имя, идентифицирующее данную строку; оно используется только в сообщениях об ошибках.
-
parse(string, name='<string>') -
Разделяет заданную строку на примеры и промежуточный текст и возвращает их в виде списка, в котором чередуются объекты
Exampleи строки. Нумерация строк для объектовExampleначинается с нуля. Необязательный аргумент name — это имя, идентифицирующее данную строку; оно используется только в сообщениях об ошибках.
-
Объекты TestResults
-
class doctest.TestResults(failed, attempted) -
-
failed -
Количество неудачных тестов.
-
attempted -
Количество запущенных тестов.
-
skipped -
Количество пропущенных тестов.
Добавлено в версии 3.13.
-
Объекты DocTestRunner
-
class doctest.DocTestRunner(checker=None, verbose=None, optionflags=0) -
Класс обработки, используемый для выполнения и проверки интерактивных примеров в объекте
DocTest.Сравнение ожидаемого и фактического вывода выполняется объектом
OutputChecker. Это сравнение можно настроить с помощью нескольких флагов параметров; дополнительные сведения см. в разделе Флаги параметров. Если флагов параметров недостаточно, сравнение также можно настроить, передав конструктору подклассOutputChecker.Отображаемый тестовым средством вывода можно контролировать двумя способами. Во-первых, функции вывода можно передать в
run(); эта функция будет вызвана со строками, которые следует отобразить. По умолчанию используетсяsys.stdout.write. Если перехвата вывода недостаточно, вывод также можно настроить, создав подкласс DocTestRunner и переопределив методыreport_start(),report_success(),report_unexpected_exception()иreport_failure().Необязательный именованный аргумент checker задаёт объект
OutputChecker(или его полную замену), используемый для сравнения ожидаемого вывода с фактическим выводом примеров doctest.Необязательный именованный аргумент verbose управляет подробностью вывода
DocTestRunner. Если verbose равенTrue, при выполнении выводится информация о каждом примере. Если verbose равенFalse, выводятся только сведения о сбоях. Если verbose не задан или равенNone, подробный вывод используется только при наличии переключателя командной строки-v.Необязательный именованный аргумент optionflags позволяет управлять тем, как средство запуска тестов сравнивает ожидаемый вывод с фактическим и как отображает сведения о сбоях. Дополнительные сведения см. в разделе Флаги параметров.
Средство запуска тестов собирает статистику. Общее число запущенных, неудачных и пропущенных примеров также доступно через атрибуты
tries,failuresиskips. Методыrun()иsummarize()возвращают экземплярTestResults.DocTestRunnerопределяет следующие методы:-
report_start(out, test, example) -
Сообщает, что средство запуска тестов собирается обработать заданный пример. Этот метод предоставлен для того, чтобы подклассы
DocTestRunnerмогли настраивать вывод; его не следует вызывать напрямую.example — пример, который будет обработан. test — тест, содержащий example. out — функция вывода, переданная в
DocTestRunner.run().
-
report_success(out, test, example, got) -
Сообщает об успешном выполнении заданного примера. Этот метод предоставлен для того, чтобы подклассы
DocTestRunnerмогли настраивать вывод; его не следует вызывать напрямую.example — пример, который будет обработан. got — фактический вывод примера. test — тест, содержащий example. out — функция вывода, переданная в
DocTestRunner.run().
-
report_failure(out, test, example, got) -
Сообщает о неудачном выполнении заданного примера. Этот метод предоставлен для того, чтобы подклассы
DocTestRunnerмогли настраивать вывод; его не следует вызывать напрямую.example — пример, который будет обработан. got — фактический вывод примера. test — тест, содержащий example. out — функция вывода, переданная в
DocTestRunner.run().
-
report_unexpected_exception(out, test, example, exc_info) -
Сообщает о том, что заданный пример вызвал непредвиденное исключение. Этот метод предоставлен для того, чтобы подклассы
DocTestRunnerмогли настраивать вывод; его не следует вызывать напрямую.example — пример, который будет обработан. exc_info — кортеж со сведениями о непредвиденном исключении (возвращаемый функцией
sys.exc_info()). test — тест, содержащий example. out — функция вывода, переданная вDocTestRunner.run().
-
run(test, compileflags=None, out=None, clear_globs=True) -
Запускает примеры из test (объекта
DocTest) и отображает результаты с помощью функции записи out. Возвращает экземплярTestResults.Примеры выполняются в пространстве имён
test.globs. Если clear_globs имеет значение true (по умолчанию), это пространство имён будет очищено после выполнения теста, чтобы облегчить сборку мусора. Если после завершения теста вам нужно проверить пространство имён, используйте clear_globs=False.compileflags задаёт набор флагов, которые компилятор Python должен использовать при выполнении примеров. Если параметр не задан, по умолчанию используется набор флагов импортов future, применимых к globs.
Вывод каждого примера проверяется средством проверки вывода
DocTestRunner, а результаты форматируются методамиDocTestRunner.report_*().
-
summarize(verbose=None) -
Выводит сводку по всем тестовым случаям, запущенным этим DocTestRunner, и возвращает экземпляр
TestResults.Необязательный аргумент verbose управляет подробностью сводки. Если уровень подробности не задан, используется уровень подробности
DocTestRunner.
DocTestParserимеет следующие атрибуты:-
tries -
Количество запущенных примеров.
-
failures -
Количество неудачных примеров.
-
skips -
Количество пропущенных примеров.
Добавлено в версии 3.13.
-
Объекты OutputChecker
-
class doctest.OutputChecker -
Класс для проверки того, соответствует ли фактический вывод примера doctest ожидаемому.
OutputCheckerопределяет два метода:check_output(), который сравнивает заданную пару выводов и возвращаетTrue, если они совпадают; иoutput_difference(), который возвращает строку с описанием различий между двумя выводами.OutputCheckerопределяет следующие методы:-
check_output(want, got, optionflags) -
Возвращает
Trueтогда и только тогда, когда фактический вывод примера (got) совпадает с ожидаемым выводом (want). Эти строки всегда считаются совпадающими, если они идентичны; однако в зависимости от флагов параметров, используемых средством запуска тестов, возможны и некоторые виды неточного совпадения. Дополнительные сведения о флагах параметров см. в разделе Флаги параметров.
-
output_difference(example, got, optionflags) -
Возвращает строку с описанием различий между ожидаемым выводом заданного примера (example) и фактическим выводом (got). optionflags — набор флагов параметров, использованных для сравнения want и got.
-
Отладка
Doctest предоставляет несколько механизмов для отладки примеров doctest:
- Некоторые функции преобразуют doctest-примеры в исполняемые программы Python, которые можно запускать в отладчике Python,
pdb. - Класс
DebugRunnerявляется подклассомDocTestRunner, который при первом неудачном примере вызывает исключение, содержащее сведения об этом примере. Эти сведения можно использовать для посмертной отладки примера. - Тестовые случаи
unittest, созданные с помощьюDocTestSuite(), поддерживают методdebug(), определённый вunittest.TestCase. -
Вы можете добавить вызов
pdb.set_trace()в пример doctest, и при выполнении этой строки вы попадёте в отладчик Python. Там можно изучить текущие значения переменных и так далее. Например, предположим, чтоa.pyсодержит только эту строку документации модуля:""" >>> def f(x): ... g(x*2) >>> def g(x): ... print(x+3) ... import pdb; pdb.set_trace() >>> f(3) 9 """
Тогда интерактивный сеанс Python может выглядеть так:
>>> import a, doctest >>> doctest.testmod(a) --Return-- > <doctest a[1]>(3)g()->None -> import pdb; pdb.set_trace() (Pdb) list 1 def g(x): 2 print(x+3) 3 -> import pdb; pdb.set_trace() [EOF] (Pdb) p x 6 (Pdb) step --Return-- > <doctest a[0]>(2)f()->None -> g(x*2) (Pdb) list 1 def f(x): 2 -> g(x*2) [EOF] (Pdb) p x 3 (Pdb) step --Return-- > <doctest a[2]>(1)?()->None -> f(3) (Pdb) cont (0, 3) >>>
Функции, которые преобразуют doctest в код Python и, возможно, запускают созданный код под отладчиком:
-
doctest.script_from_examples(s) -
Преобразовать текст с примерами в скрипт.
Аргумент s — строка, содержащая примеры doctest. Строка преобразуется в скрипт Python: примеры doctest в s преобразуются в обычный код, а всё остальное — в комментарии Python. Созданный скрипт возвращается в виде строки. Например,
import doctest print(doctest.script_from_examples(r""" Set x and y to 1 and 2. >>> x, y = 1, 2 Print their sum: >>> print(x+y) 3 """))выводит:
# Set x and y to 1 and 2. x, y = 1, 2 # # Print their sum: print(x+y) # Expected: ## 3
Эта функция используется внутри других функций (см. ниже), но также может быть полезна, если вы хотите преобразовать интерактивный сеанс Python в скрипт Python.
-
doctest.testsource(module, name) -
Преобразовать doctest объекта в скрипт.
Аргумент module — объект модуля или имя модуля с точечной нотацией, содержащего объект, doctest которого вас интересует. Аргумент name — имя объекта (внутри модуля), doctest которого вас интересует. Результат — строка, содержащая строку документации объекта, преобразованную в скрипт Python, как описано выше для
script_from_examples(). Например, если модульa.pyсодержит функцию верхнего уровняf(), тоimport a, doctest print(doctest.testsource(a, "a.f"))
выводит версию строки документации функции
f()в виде скрипта: doctest-примеры преобразуются в код, а остальной текст помещается в комментарии.
-
doctest.debug(module, name, pm=False) -
Отладить doctest объекта.
Аргументы module и name такие же, как у описанной выше функции
testsource(). Созданный скрипт Python для строки документации указанного объекта записывается во временный файл, после чего этот файл запускается под управлением отладчика Python,pdb.Для локального и глобального контекста выполнения используется неглубокая копия
module.__dict__.Необязательный аргумент pm определяет, используется ли посмертная отладка. Если pm имеет истинное значение, файл скрипта запускается напрямую, и отладчик подключается, только если выполнение скрипта завершается из-за необработанного исключения. В этом случае запускается посмертная отладка с помощью
pdb.post_mortem(), которому передаётся объект трассировки стека из необработанного исключения. Если pm не указан или имеет ложное значение, скрипт запускается под отладчиком с самого начала: для этого вpdb.run()передаётся соответствующий вызовexec().
-
doctest.debug_src(src, pm=False, globs=None) -
Отладить doctest в строке.
Эта функция похожа на описанную выше функцию
debug(), за исключением того, что строка с примерами doctest указывается напрямую с помощью аргумента src.Необязательный аргумент pm имеет то же значение, что и в описанной выше функции
debug().Необязательный аргумент globs задаёт словарь, используемый и как локальный, и как глобальный контекст выполнения. Если он не указан или равен
None, используется пустой словарь. Если он указан, используется его неглубокая копия.
Класс DebugRunner и специальные исключения, которые он может вызывать, представляют наибольший интерес для авторов тестовых инфраструктур, поэтому здесь они описаны лишь в общих чертах. Подробнее см. исходный код и особенно строку документации DebugRunner (которая сама является doctest!):
-
class doctest.DebugRunner(checker=None, verbose=None, optionflags=0) -
Подкласс
DocTestRunner, который вызывает исключение при первой же обнаруженной ошибке. Если возникает неожиданное исключение, вызывается исключениеUnexpectedException, содержащее тест, пример и исходное исключение. Если вывод не совпадает, вызывается исключениеDocTestFailure, содержащее тест, пример и фактический вывод.Сведения о параметрах конструктора и методах см. в документации для
DocTestRunnerв разделе Расширенный API.
Экземпляры DebugRunner могут вызывать два исключения:
-
exception doctest.DocTestFailure(test, example, got) -
Исключение, вызываемое
DocTestRunner, чтобы сообщить, что фактический вывод примера doctest не совпал с ожидаемым. Аргументы конструктора используются для инициализации атрибутов с такими же именами.
DocTestFailure определяет следующие атрибуты:
-
DocTestFailure.test -
Объект
DocTest, выполнявшийся в момент сбоя примера.
-
DocTestFailure.example -
Пример
Example, завершившийся ошибкой.
-
DocTestFailure.got -
Фактический вывод примера.
-
exception doctest.UnexpectedException(test, example, exc_info) -
Исключение, вызываемое
DocTestRunner, чтобы сообщить, что пример doctest вызвал неожиданное исключение. Аргументы конструктора используются для инициализации атрибутов с такими же именами.
UnexpectedException определяет следующие атрибуты:
-
UnexpectedException.test -
Объект
DocTest, выполнявшийся в момент сбоя примера.
-
UnexpectedException.example -
Пример
Example, завершившийся ошибкой.
-
UnexpectedException.exc_info -
Кортеж со сведениями о неожиданном исключении, возвращаемый функцией
sys.exc_info().
Несколько слов в защиту doctest
Как упоминалось во введении, doctest со временем стал применяться преимущественно в трёх целях:
- Проверка примеров в строках документации.
- Регрессионное тестирование.
- Исполняемая документация / литературное тестирование.
У этих целей разные требования, и важно их различать. В частности, если заполнять строки документации малопонятными тестовыми случаями, документация получится плохой.
При написании строки документации тщательно выбирайте примеры. Это своего рода искусство, которому нужно научиться: поначалу оно может даваться нелегко. Примеры должны приносить документации реальную пользу. Удачный пример зачастую способен заменить множество слов. Если подойти к делу внимательно, примеры станут незаменимыми для пользователей, а время, затраченное на их сбор, многократно окупится с годами, по мере изменений. Я до сих пор удивляюсь, как часто один из моих примеров doctest перестаёт работать после «безобидного» изменения.
Doctest также отлично подходит для регрессионного тестирования, особенно если не экономить на пояснительном тексте. Чередуя прозу и примеры, гораздо проще отслеживать, что именно проверяется и почему. Если тест завершается ошибкой, хорошие пояснения могут значительно облегчить понимание проблемы и способов её устранения. Конечно, можно писать подробные комментарии к тестам, основанным на коде, но так поступают немногие программисты. Многие обнаружили, что использование doctest позволяет писать гораздо более понятные тесты. Возможно, всё дело лишь в том, что писать прозу с помощью doctest немного проще, чем код, тогда как добавлять комментарии в код немного сложнее. Но, думаю, причина глубже: при написании теста на основе doctest естественно стремиться объяснить тонкие особенности программы и проиллюстрировать их примерами. В результате тестовые файлы естественным образом начинаются с простейших возможностей, а затем логично переходят к более сложным случаям и пограничным условиям. В итоге получается связное повествование, а не набор изолированных функций, которые проверяют отдельные фрагменты функциональности, казалось бы, случайным образом. Это иной подход, дающий иные результаты и размывающий границу между тестированием и объяснением.
Регрессионное тестирование лучше ограничить выделенными объектами или файлами. Существует несколько способов организации тестов:
- Создавайте текстовые файлы с тестовыми случаями в виде интерактивных примеров и проверяйте эти файлы с помощью
testfile()илиDocFileSuite(). Этот способ рекомендуется, хотя проще всего его применять в новых проектах, изначально разработанных с расчётом на doctest. - Определяйте функции с именами
_regrtest_topic, состоящие из одной строки документации с тестовыми случаями для соответствующих тем. Эти функции можно включить в тот же файл, что и модуль, или вынести в отдельный файл тестов. - Определяйте словарь
__test__, сопоставляющий темы регрессионных тестов со строками документации, содержащими тестовые случаи.
Если вы поместили тесты в модуль, сам модуль может выступать в качестве средства запуска тестов. При сбое теста можно настроить средство запуска так, чтобы во время отладки проблемы оно повторно запускало только завершившийся с ошибкой doctest. Вот минимальный пример такого средства запуска тестов:
if __name__ == '__main__':
import doctest
flags = doctest.REPORT_NDIFF|doctest.FAIL_FAST
if len(sys.argv) > 1:
name = sys.argv[1]
if name in globals():
obj = globals()[name]
else:
obj = __test__[name]
doctest.run_docstring_examples(obj, globals(), name=name,
optionflags=flags)
else:
fail, total = doctest.testmod(optionflags=flags)
print(f"{fail} failures out of {total} tests")
Сноски
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/library/doctest.html