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 есть множество примеров доктестов. Особенно полезные примеры можно найти в стандартном тестовом файле Lib/test/test_doctest/test_doctest.py
.
Простое использование: Проверка примеров в документах¶
Самый простой способ начать использовать doctest (но не обязательно тот, которым вы будете продолжать это делать) - заканчивать каждый модуль M
символом:
if __name__ == "__main__":
import doctest
doctest.testmod()
doctest
, а затем просматривает строки документов в модуле M
.
Запуск модуля в качестве скрипта приводит к выполнению и проверке примеров, приведенных в документации:
python M.py
Это не выведет ничего, пока пример не потерпит неудачу, в этом случае пример(ы), потерпевший(ие) неудачу, и причина(ы) неудачи будут выведены в stdout, и последняя строка вывода будет ***Test Failed*** N failures.
, где N - количество примеров, потерпевших неудачу.
Запустите его с помощью переключателя -v
:
python M.py -v
и подробный отчет обо всех испробованных примерах будет выведен на стандартный вывод, а в конце будут приведены различные резюме.
Вы можете включить режим verbose, передав verbose=True
в testmod()
, или запретить его, передав verbose=False
. В любом из этих случаев sys.argv
не рассматривается testmod()
(поэтому передача -v
или отсутствие таковой не имеет никакого эффекта).
Существует также ярлык командной строки для запуска testmod()
. Вы можете поручить интерпретатору Python запустить модуль doctest непосредственно из стандартной библиотеки и передать имя (имена) модуля в командной строке:
python -m doctest -v example.py
Это позволит импортировать example.py
как отдельный модуль и запустить testmod()
на нем. Обратите внимание, что это может работать некорректно, если файл является частью пакета и импортирует другие подмодули из этого пакета.
Более подробную информацию о testmod()
см. в разделе Базовый API.
Простое использование: Проверка примеров в текстовом файле¶
Еще одно простое применение doctest - тестирование интерактивных примеров в текстовом файле. Это можно сделать с помощью функции testfile()
:
import doctest
doctest.testfile("example.txt")
Этот короткий скрипт выполняет и проверяет все интерактивные примеры Python, содержащиеся в файле example.txt
. Содержимое файла рассматривается так, как если бы это была одна огромная doc-строка; файл не обязательно должен содержать программу на 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()
ничего не отображает, если пример не сработал. Если пример не работает, то неудачный пример(ы) и причина(ы) неудачи выводятся в stdout, используя тот же формат, что и testmod()
.
По умолчанию testfile()
ищет файлы в каталоге вызывающего модуля. Описание дополнительных аргументов, с помощью которых можно указать модулю искать файлы в других местах, см. в разделе Базовый API.
Как и в testmod()
, многословность testfile()
можно задать с помощью переключателя командной строки -v
или с помощью необязательного ключевого аргумента verbose.
Существует также ярлык командной строки для запуска testfile()
. Вы можете поручить интерпретатору Python запустить модуль doctest непосредственно из стандартной библиотеки и передать имя(а) файла(ов) в командной строке:
python -m doctest -v example.txt
Поскольку имя файла не заканчивается на .py
, из doctest
следует, что он должен быть запущен с помощью testfile()
, а не testmod()
.
Более подробную информацию о testfile()
см. в разделе Базовый API.
Как это работает¶
В этом разделе подробно рассматривается, как работает doctest: какие документальные строки он просматривает, как он находит интерактивные примеры, какой контекст выполнения он использует, как он обрабатывает исключения и как флаги опций могут использоваться для управления его поведением. Это информация, которую вам нужно знать для написания примеров doctest; о том, как запустить doctest на этих примерах, читайте в следующих разделах.
Какие строки документов рассматриваются?¶
Поиск производится в строке документа модуля, а также во всех строках документа функций, классов и методов. Объекты, импортированные в модуль, не ищутся.
Кроме того, бывают случаи, когда вы хотите, чтобы тесты были частью модуля, но не частью текста справки, что требует, чтобы тесты не были включены в docstring. Doctest ищет переменную уровня модуля под названием __test__
и использует ее для поиска других тестов. Если M.__test__
существует, она должна быть dict, и каждая запись сопоставляет (строковое) имя с объектом функции, объектом класса или строкой. Из 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
рекурсивно ищет в них док-строки, которые затем проверяются на наличие тестов.
Любые найденные классы подвергаются рекурсивному поиску, чтобы проверить документацию в содержащихся в них методах и вложенных классах.
Как распознаются примеры Docstring?¶
В большинстве случаев копирование и вставка интерактивной консольной сессии работает хорошо, но 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-колоночных упоров табуляции. Табуляции в выводе, сгенерированном тестируемым кодом, не изменяются. Поскольку все жесткие табуляции в примере вывода расширяются, это означает, что если в выводе кода присутствуют жесткие табуляции, тест может пройти только в том случае, если действует опция
NORMALIZE_WHITESPACE
или directive. Как вариант, тест можно переписать, чтобы перехватывать вывод и сравнивать его с ожидаемым значением в рамках теста. К такому способу работы с табуляциями в исходном тексте мы пришли методом проб и ошибок, и он оказался наименее подверженным ошибкам. Можно использовать другой алгоритм работы с табуляциями, написав собственный классDocTestParser
.Перехватывается вывод в stdout, но не вывод в stderr (трассировка исключений перехватывается другим способом).
Если вы продолжаете строку с помощью обратной косой черты в интерактивном сеансе или по какой-либо другой причине используете обратную косую черту, вам следует использовать сырой docstring, который сохранит ваши обратные косые черты именно в том виде, в каком вы их набрали:
>>> 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
находит docstring для тестирования, он использует малую копию глобалов M
, чтобы запуск тестов не изменял реальные глобалы модуля, и чтобы один тест в M
не мог оставить крохи, которые случайно позволят сработать другому тесту. Это означает, что примеры могут свободно использовать любые имена, определенные на верхнем уровне в M
, а также имена, определенные ранее в выполняемой docstring. Примеры не могут видеть имена, определенные в других документах.
Вы можете принудительно использовать свой собственный dict в качестве контекста выполнения, передав вместо него 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
Этот доктест проходит успешно, если поднимается ValueError
, а при list.remove(x): x not in list
- как показано на рисунке.
Ожидаемый вывод для исключения должен начинаться с заголовка traceback, который может состоять из двух следующих строк, расположенных с тем же отступом, что и первая строка примера:
Traceback (most recent call last):
Traceback (innermost last):
За заголовком traceback следует необязательный стек traceback, содержимое которого игнорируется 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
или пример просто выводит текст трассировки. На практике обычный вывод редко начинается со строки заголовка traceback, так что это не создает реальных проблем.Каждая строка стека возвратов трассировки (если она присутствует) должна быть отступлена дальше первой строки примера, или начинаться с неалфавитно-цифрового символа. Первая строка, следующая за заголовком трассировки с тем же отступом и начинающаяся с буквенно-цифрового символа, принимается за начало детализации исключения. Конечно, это правильно для настоящих возвратов трассировки.
Если указана опция
IGNORE_EXCEPTION_DETAIL
doctest, все, что следует за крайним левым двоеточием, и любая информация о модуле в имени исключения игнорируется.Интерактивная оболочка опускает строку заголовка traceback для некоторых
SyntaxError
s. Но doctest использует строку заголовка traceback, чтобы отличить исключения от неисключений. Поэтому в редких случаях, когда вам нужно протестироватьSyntaxError
, в котором отсутствует заголовок traceback, вам придется вручную добавить строку заголовка traceback в ваш тестовый пример.
Для некоторых исключений 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. Символические имена флагов предоставляются в виде констант модуля, которые могут быть bitwise ORed вместе и переданы в различные функции. Имена также могут использоваться в doctest directives и передаваться в интерфейс командной строки doctest с помощью опции -o
.
Added in version 3.4: Опция командной строки -o
.
Первая группа опций определяет семантику теста, контролируя аспекты того, как doctest решает, соответствует ли фактический результат ожидаемому результату примера:
- doctest.DONT_ACCEPT_TRUE_FOR_1¶
По умолчанию, если ожидаемый выходной блок содержит только
1
, то фактический выходной блок, содержащий только1
или толькоTrue
, считается совпадающим, и аналогично для0
иFalse
. Если указаноDONT_ACCEPT_TRUE_FOR_1
, ни одна из подстановок не допускается. Поведение по умолчанию учитывает то, что Python изменил возвращаемый тип многих функций с целого числа на булево; доктесты, ожидающие вывода «маленького целого», по-прежнему работают в этих случаях. Возможно, эта опция исчезнет, но не раньше, чем через несколько лет.
- doctest.DONT_ACCEPT_BLANKLINE¶
По умолчанию, если блок ожидаемого вывода содержит строку, содержащую только строку
<BLANKLINE>
, то эта строка будет соответствовать пустой строке в реальном выводе. Поскольку действительно пустая строка ограничивает ожидаемый вывод, это единственный способ сообщить, что ожидается пустая строка. При указанииDONT_ACCEPT_BLANKLINE
такая подстановка не допускается.
- doctest.NORMALIZE_WHITESPACE¶
Если указано, все последовательности пробельных символов (пробелы и новые строки) рассматриваются как одинаковые. Любая последовательность пробельных символов в ожидаемом выводе будет соответствовать любой последовательности пробельных символов в фактическом выводе. По умолчанию пробельные символы должны совпадать в точности.
NORMALIZE_WHITESPACE
особенно полезен, когда строка ожидаемого вывода очень длинная, и вы хотите развернуть ее на несколько строк в вашем исходном тексте.
- doctest.ELLIPSIS¶
Если указано, то маркер многоточия (
...
) в ожидаемом выводе может соответствовать любой подстроке в фактическом выводе. Сюда входят подстроки, выходящие за границы строк, и пустые подстроки, поэтому лучше всего использовать эту функцию просто. Сложное использование может привести к таким же сюрпризам, как и.*
в регулярных выражениях: «Упс, совпало слишком много!».
- doctest.IGNORE_EXCEPTION_DETAIL¶
Если указано, то доктесты, ожидающие исключения, проходят до тех пор, пока возникает исключение ожидаемого типа, даже если детали (сообщение и полное имя исключения) не совпадают.
Например, пример, ожидающий
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¶
Если флаг указан, выйдите после первого неудачного примера и не пытайтесь выполнить остальные примеры. Таким образом, количество сообщений о неудачах будет не более 1. Этот флаг может быть полезен при отладке, так как примеры после первой неудачи даже не выдадут отладочных результатов.
Командная строка doctest принимает опцию
-f
как сокращение для-o FAIL_FAST
.Added in version 3.4.
- doctest.REPORTING_FLAGS¶
Битовая маска, объединяющая все вышеперечисленные флаги отчетов.
Также есть возможность регистрировать новые имена флагов опций, хотя это не имеет смысла, если вы не собираетесь расширять внутреннюю структуру doctest
с помощью подклассов:
- doctest.register_optionflag(name)¶
Создает новый флаг опции с заданным именем и возвращает целочисленное значение нового флага.
register_optionflag()
можно использовать при подклассификацииOutputChecker
илиDocTestRunner
для создания новых опций, которые поддерживаются вашими подклассами.register_optionflag()
всегда следует вызывать, используя следующую идиому:MY_FLAG = register_optionflag('MY_FLAG')
Директивы¶
Директивы Doctest могут быть использованы для изменения option flags для отдельного примера. Директивы 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
серьезно относится к требованию точного совпадения в ожидаемом выводе. Если хотя бы один символ не совпадает, тест проваливается. Возможно, это несколько раз удивит вас, когда вы узнаете, что именно 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 здесь сильно различаются по качеству.
>>> 1./7 # risky
0.14285714285714285
>>> print(1./7) # safer
0.142857142857
>>> print(round(1./7, 6)) # much safer
0.142857
Числа вида I/2.**J
безопасны на всех платформах, и я часто придумываю примеры-доктесты для получения чисел такого вида:
>>> 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’а создается новая неглубокая копия этой dict, так что его примеры начинаются с чистого листа. По умолчанию или если
None
, используется новый пустой dict.Необязательный аргумент extraglobs дает дикту, объединенную с глобалами, используемыми для выполнения примеров. Это работает как
dict.update()
: если globs и extraglobs имеют общий ключ, то связанное с ним значение в extraglobs появляется в объединенном dict. По умолчанию или еслиNone
, дополнительные глобалы не используются. Это расширенная возможность, позволяющая параметризовать доктесты. Например, доктест может быть написан для базового класса с использованием общего имени класса, а затем повторно использован для проверки любого количества подклассов путем передачи диктанта extraglobs, сопоставляющего общее имя с проверяемым подклассом.Необязательный аргумент verbose выводит много информации, если true, и выводит только ошибки, если false; по умолчанию, или если
None
, то true тогда и только тогда, когда'-v'
находится вsys.argv
.Необязательный аргумент report печатает резюме в конце, если оно истинно, в противном случае в конце ничего не печатается. В режиме verbose сводка будет подробной, в противном случае - очень краткой (фактически пустой, если все тесты пройдены).
Необязательный аргумент optionflags (значение по умолчанию 0) принимает значение bitwise OR из флагов опций. См. раздел Флаги опций.
Необязательный аргумент raise_on_error по умолчанию имеет значение false. Если значение равно true, исключение будет поднято при первом сбое или неожиданном исключении в примере. Это позволяет отлаживать неудачи после их завершения. Поведение по умолчанию - продолжать выполнение примеров.
Необязательный аргумент parser задает
DocTestParser
(или подкласс), который должен использоваться для извлечения тестов из файлов. По умолчанию используется обычный парсер (т.е.DocTestParser()
).Необязательный аргумент encoding задает кодировку, которая должна использоваться для преобразования файла в юникод.
- 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__
.Также проверяются примеры, достижимые из dict
m.__test__
, если он существует.m.__test__
сопоставляет имена (строки) с функциями, классами и строками; примеры ищутся в документах функций и классов; строки ищутся напрямую, как если бы они были документами.Поиск ведется только в документах, прикрепленных к объектам, принадлежащим модулю m.
Возврат
(failure_count, test_count)
.Необязательный аргумент name задает имя модуля; по умолчанию или если используется
None
, тоm.__name__
.Необязательный аргумент exclude_empty по умолчанию имеет значение false. Если значение равно true, то объекты, для которых не найдено ни одного док-теста, исключаются из рассмотрения. Значение по умолчанию - это хак обратной совместимости, так что код, все еще использующий
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()
выше.
Unittest API¶
По мере роста вашей коллекции doctest’овских модулей вам понадобится способ систематического выполнения всех их doctest’ов. doctest
предоставляет две функции, которые можно использовать для создания unittest
тестовых наборов из модулей и текстовых файлов, содержащих доктесты. Чтобы интегрироваться с 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.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
. Функция setUp может получить доступ к глобалам тестов через атрибут globs переданного теста.Необязательный аргумент globs - это словарь, содержащий начальные глобальные переменные для тестов. Для каждого теста создается новая копия этого словаря. По умолчанию globs - это новый пустой словарь.
Необязательный аргумент optionflags задает опции doctest по умолчанию для тестов, созданные путем объединения отдельных флагов опций. См. раздел Флаги опций. См. функцию
set_unittest_reportflags()
ниже для более удобного способа установки опций отчетов.Необязательный аргумент parser задает
DocTestParser
(или подкласс), который должен использоваться для извлечения тестов из файлов. По умолчанию используется обычный парсер (т.е.DocTestParser()
).Необязательный аргумент encoding задает кодировку, которая должна использоваться для преобразования файла в юникод.
Глобал
__file__
добавляется к глобалам, предоставляемым доктестам, загруженным из текстового файла с помощьюDocFileSuite()
.
- 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 и выполняет каждый доктест в модуле. Если какой-либо из доктестов не работает, то синтезированный юнит-тест терпит неудачу, и возникает исключениеfailureException
, показывающее имя файла, содержащего тест, и (иногда приблизительный) номер строки. Если все примеры в docstring пропущены, то синтезированный модульный тест также помечается как пропущенный.Необязательный аргумент module указывает тестируемый модуль. Это может быть объект модуля или имя модуля (возможно, с точкой). Если он не указан, используется модуль, вызывающий эту функцию.
Необязательный аргумент globs - это словарь, содержащий начальные глобальные переменные для тестов. Для каждого теста создается новая копия этого словаря. По умолчанию globs - это новый пустой словарь.
Необязательный аргумент extraglobs задает дополнительный набор глобальных переменных, который объединяется в globs. По умолчанию дополнительные глобальные переменные не используются.
Необязательный аргумент test_finder - это объект
DocTestFinder
(или его замена), который используется для извлечения доктестов из модуля.Необязательные аргументы setUp, tearDown и optionflags такие же, как и для функции
DocFileSuite()
выше.Эта функция использует ту же технику поиска, что и
testmod()
.Изменено в версии 3.5:
DocTestSuite()
возвращает пустойunittest.TestSuite
, если модуль не содержит docstrings, вместо того, чтобы поднятьValueError
.
- exception doctest.failureException¶
Когда тесты, которые были преобразованы в юнит-тесты с помощью
DocFileSuite()
илиDocTestSuite()
, терпят неудачу, возникает это исключение с указанием имени файла, содержащего тест, и (иногда приблизительного) номера строки.
Под обложкой 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 принимает bitwise OR флагов опций. См. раздел Флаги опций. Можно использовать только «флаги отчетов».
Это глобальная настройка модуля, которая влияет на все будущие доктесты, выполняемые модулем
unittest
: методrunTest()
изDocTestCase
смотрит на флаги опций, указанные для тестового случая при создании экземпляраDocTestCase
. Если флаги отчетности не были указаны (что является типичным и ожидаемым случаем), флаги отчетностиdoctest
изunittest
преобразуются bitwise ORed во флаги опций, и дополненные таким образом флаги опций передаются экземпляруDocTestRunner
, созданному для выполнения теста. Если при создании экземпляраDocTestCase
были указаны какие-либо флаги отчетности, флаги отчетностиdoctest
вunittest
игнорируются.Функция возвращает значение флагов отчетности
unittest
, действовавших до вызова функции.
Расширенный API¶
Базовый API - это простая обертка, которая призвана упростить использование doctest. Он достаточно гибкий и должен удовлетворить потребности большинства пользователей; однако если вам требуется более тонкий контроль над тестированием или вы хотите расширить возможности doctest, то вам следует использовать расширенный API.
Расширенное API вращается вокруг двух контейнерных классов, которые используются для хранения интерактивных примеров, извлеченных из примеров doctest:
Example
: Одиночный Python statement в паре с его ожидаемым результатом.DocTest
: КоллекцияExample
s, обычно извлекаемая из одной строки документа или текстового файла.
Определены дополнительные классы обработки для поиска, разбора, запуска и проверки примеров doctest:
DocTestFinder
: Находит все документограммы в данном модуле и используетDocTestParser
для созданияDocTest
из каждой документограммы, содержащей интерактивные примеры.DocTestParser
: Создает объектDocTest
из строки (например, docstring объекта).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¶
Пространство имен (оно же globals), в котором должны выполняться примеры. Это словарь, отображающий имена на значения. Любые изменения в пространстве имен, сделанные примерами (например, привязка новых переменных), будут отражены в
globs
после выполнения теста.
- name¶
Строковое имя, идентифицирующее
DocTest
. Обычно это имя объекта или файла, из которого был извлечен тест.
- filename¶
Имя файла, из которого был извлечен этот
DocTest
; илиNone
, если имя файла неизвестно, или еслиDocTest
не был извлечен из файла.
- lineno¶
Номер строки в пределах
filename
, с которой начинается данныйDocTest
, илиNone
, если номер строки недоступен. Номер строки равен нулю по отношению к началу файла.
- docstring¶
Строка, из которой был извлечен тест, или
None
, если строка недоступна, или если тест не был извлечен из строки.
Примеры объектов¶
- class doctest.Example(source, want, exc_msg=None, lineno=0, indent=0, options=None)¶
Один интерактивный пример, состоящий из оператора Python и его ожидаемого вывода. Аргументы конструктора используются для инициализации одноименных атрибутов.
Example
определяет следующие атрибуты. Они инициализируются конструктором и не должны изменяться напрямую.- source¶
Строка, содержащая исходный код примера. Этот исходный код состоит из одного оператора Python и всегда заканчивается новой строкой; конструктор добавляет новую строку, когда это необходимо.
- want¶
Ожидаемый результат выполнения исходного кода примера (либо из stdout, либо трассировка в случае исключения).
want
заканчивается новой строкой, если не ожидается никакого вывода, в этом случае это пустая строка. Конструктор добавляет новую строку, когда это необходимо.
- exc_msg¶
Сообщение об исключении, сгенерированное примером, если ожидается, что пример сгенерирует исключение; или
None
, если не ожидается, что он сгенерирует исключение. Это сообщение об исключении сравнивается с возвращаемым значениемtraceback.format_exception_only()
.exc_msg
заканчивается новой строкой, если это неNone
. Конструктор добавляет новую строку, если это необходимо.
- lineno¶
Номер строки в строке, содержащей данный пример, с которой начинается пример. Номер строки равен нулю по отношению к началу содержащей строки.
- indent¶
Отступ примера в содержащейся строке, т.е. количество пробельных символов, которые предшествуют первой строке примера.
- options¶
Словарь, отображающий флаги опций на
True
илиFalse
, который используется для переопределения опций по умолчанию в данном примере. Любые флаги опций, не содержащиеся в этом словаре, оставляются в значении по умолчанию (как указано вDocTestRunner
в optionflags). По умолчанию никакие опции не установлены.
Объекты DocTestFinder¶
- class doctest.DocTestFinder(verbose=False, parser=DocTestParser(), recurse=True, exclude_empty=True)¶
Класс обработки, используемый для извлечения
DocTest
s, относящихся к данному объекту, из его docstring и docstrings содержащихся в нем объектов.DocTest
s может быть извлечен из модулей, классов, функций, методов, статических методов, методов класса и свойств.Необязательный аргумент verbose может использоваться для вывода информации об объектах, которые ищет искатель. По умолчанию он принимает значение
False
(вывод не производится).Необязательный аргумент parser задает объект
DocTestParser
(или его замену), который используется для извлечения доктестов из строк документов.Если необязательный аргумент recurse равен false, то
DocTestFinder.find()
будет исследовать только данный объект, а не все содержащиеся в нем объекты.Если дополнительный аргумент exclude_empty равен false, то в
DocTestFinder.find()
будут включены тесты для объектов с пустыми документами.DocTestFinder
определяет следующий метод:- find(obj[, name][, module][, globs][, extraglobs])¶
Возвращает список
DocTest
, которые определены документом obj или документом любого из содержащихся в нем объектов.Необязательный аргумент name задает имя объекта; это имя будет использоваться для построения имен возвращаемых
DocTest
s. Если name не указано, то используетсяobj.__name__
.Необязательный параметр module - это модуль, содержащий данный объект. Если модуль не указан или имеет значение
None
, то программа поиска тестов попытается автоматически определить правильный модуль. Используется модуль объекта:В качестве пространства имен по умолчанию, если не указано globs.
Чтобы предотвратить извлечение DocTestFinder’ом DocTests’ов из объектов, импортированных из других модулей. (Содержащиеся объекты с модулями, отличными от module, игнорируются).
Чтобы найти имя файла, содержащего объект.
Помогает найти номер строки объекта в его файле.
Если module имеет значение
False
, попытка найти модуль не предпринимается. Это неясно и используется в основном для тестирования самого doctest: если module равенFalse
или равенNone
, но не может быть найден автоматически, то считается, что все объекты принадлежат (несуществующему) модулю, поэтому все содержащиеся в нем объекты будут (рекурсивно) искаться для doctest’ов.Словарь globals для каждого
DocTest
формируется путем объединения globs и extraglobs (привязки в extraglobs переопределяют привязки в globs). Для каждогоDocTest
создается новая неглубокая копия словаря globals. Если 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
. Номера строк основаны на 0. Необязательный аргумент name - это имя, идентифицирующее данную строку, и используется только для сообщений об ошибках.
- parse(string, name='<string>')¶
Разделите заданную строку на примеры и промежуточный текст и верните их в виде списка чередующихся
Example
s и строк. Номера строк дляExample
s основаны на 0. Необязательный аргумент name - это имя, идентифицирующее данную строку, и используется только для сообщений об ошибках.
Объекты TestResults¶
Объекты 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 - тест, содержащий пример. 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 при выполнении примеров. Если он не указан, то по умолчанию будет использоваться набор флагов будущего импорта, который применяется к globs.
Вывод каждого примера проверяется с помощью средства проверки вывода
DocTestRunner
, а результаты форматируются с помощью методовDocTestRunner.report_*()
.
- summarize(verbose=None)¶
Выведите сводку всех тестовых случаев, которые были запущены этим DocTestRunner, и верните экземпляр
TestResults
.Необязательный аргумент verbose определяет, насколько подробным будет резюме. Если значение verbosity не указано, то используется значение verbosity
DocTestRunner
.
DocTestParser
имеет следующие атрибуты:- tries¶
Количество примеров попыток.
- failures¶
Количество неудачных примеров.
- skips¶
Количество пропущенных примеров.
Added in version 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:
Несколько функций преобразуют доктесты в исполняемые Python-программы, которые можно запускать в отладчике Python,
pdb
.Класс
DebugRunner
является подклассом классаDocTestRunner
, который вызывает исключение для первого неудачного примера, содержащее информацию о нем. Эта информация может быть использована для посмертной отладки примера.Случаи
unittest
, созданныеDocTestSuite()
, поддерживают методdebug()
, определенныйunittest.TestCase
.Вы можете добавить вызов
pdb.set_trace()
в пример doctest, и при выполнении этой строки вы попадете в отладчик Python. Затем вы сможете проверить текущие значения переменных и так далее. Например, предположим, чтоa.py
содержит только этот модуль docstring:""" >>> 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) >>>
Функции, преобразующие доктесты в код Python и, возможно, запускающие синтезированный код в отладчике:
- doctest.script_from_examples(s)¶
Преобразуйте текст с примерами в скрипт.
Аргумент s - это строка, содержащая примеры доктестов. Строка преобразуется в 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)¶
Преобразуйте доктест для объекта в скрипт.
Аргумент module - это объект модуля или точечное имя модуля, содержащего объект, чьи доктесты интересуют. Аргумент name - это имя (в модуле) объекта с интересующими доктестами. Результатом будет строка, содержащая doc-строку объекта, преобразованную в Python-скрипт, как описано выше для
script_from_examples()
. Например, если модульa.py
содержит функцию верхнего уровняf()
, тоimport a, doctest print(doctest.testsource(a, "a.f"))
печатает скриптовую версию документальной строки функции
f()
, в которой доктесты преобразованы в код, а остальное помещено в комментарии.
- doctest.debug(module, name, pm=False)¶
Отладка доктестов для объекта.
Аргументы модуль и имя такие же, как и в функции
testsource()
выше. Синтезированный Python-скрипт для docstring именованного объекта записывается во временный файл, а затем этот файл запускается под управлением отладчика Python,pdb
.Неглубокая копия
module.__dict__
используется как для локального, так и для глобального контекста выполнения.Необязательный аргумент pm определяет, используется ли посмертная отладка. Если pm имеет значение true, то файл сценария запускается напрямую, и отладчик подключается только в том случае, если сценарий завершается, вызвав необработанное исключение. Если это произойдет, то будет вызвана посмертная отладка через
pdb.post_mortem()
, передавая объект traceback из необработанного исключения. Если pm не указан или равен false, то скрипт запускается под отладчиком с самого начала, передавая соответствующий вызовexec()
вpdb.run()
.
- doctest.debug_src(src, pm=False, globs=None)¶
Отладка доктестов в строке.
Это похоже на функцию
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.got¶
Фактический результат примера.
- exception doctest.UnexpectedException(test, example, exc_info)¶
Исключение, вызываемое
DocTestRunner
для сигнализации о том, что пример doctest вызвал неожиданное исключение. Аргументы конструктора используются для инициализации одноименных атрибутов.
UnexpectedException
определяет следующие атрибуты:
- UnexpectedException.exc_info¶
Кортеж, содержащий информацию о неожиданном исключении, как возвращено
sys.exc_info()
.
Мыльница¶
Как уже говорилось во введении, у doctest
появилось три основных варианта использования:
Проверка примеров в документах.
Регрессионное тестирование.
Выполнимая документация / грамотное тестирование.
У этих вариантов использования разные требования, и их важно различать. В частности, заполнение документации непонятными тестовыми случаями делает документацию плохой.
При написании docstring выбирайте примеры docstring с осторожностью. В этом есть свое искусство, которому нужно научиться - поначалу это может быть неестественно. Примеры должны добавлять реальную ценность документации. Хороший пример часто стоит многих слов. Если все сделано аккуратно, примеры будут бесценны для ваших пользователей и многократно окупят время, потраченное на их сбор, с годами и изменениями. Я до сих пор удивляюсь тому, как часто один из моих примеров doctest
перестает работать после «безобидного» изменения.
Doctest также является отличным инструментом для регрессионного тестирования, особенно если вы не скупитесь на пояснительный текст. Чередуя прозу и примеры, становится гораздо проще отслеживать, что именно тестируется и почему. Когда тест не работает, с помощью хорошей прозы гораздо проще понять, в чем проблема и как ее исправить. Правда, при тестировании на основе кода можно писать обширные комментарии, но мало кто из программистов так делает. Многие обнаружили, что использование подходов doctest вместо этого приводит к гораздо более четким тестам. Возможно, это просто потому, что doctest делает написание прозы немного проще, чем написание кода, в то время как написание комментариев в коде немного сложнее. Я думаю, что дело не только в этом: естественное отношение при написании тестов на основе doctest заключается в том, что вы хотите объяснить тонкости вашего программного обеспечения и проиллюстрировать их примерами. Это, в свою очередь, естественным образом приводит к тому, что тестовые файлы начинаются с самых простых функций и логично переходят к сложностям и крайним случаям. В результате получается связное повествование, а не набор изолированных функций, которые тестируют отдельные фрагменты функциональности, казалось бы, наугад. Это другое отношение, и оно дает другие результаты, размывая различие между тестированием и объяснением.
Регрессионное тестирование лучше всего проводить в специальных объектах или файлах. Существует несколько вариантов организации тестов:
Напишите текстовые файлы, содержащие тестовые случаи, в качестве интерактивных примеров и протестируйте файлы, используя
testfile()
илиDocFileSuite()
. Это рекомендуется, хотя проще всего это сделать для новых проектов, с самого начала рассчитанных на использование doctest.Определите функции с именем
_regrtest_topic
, которые состоят из одиночных документов, содержащих тестовые случаи для названных тем. Эти функции могут быть включены в тот же файл, что и модуль, или выделены в отдельный тестовый файл.Определите отображение
__test__
в словаре от тем регрессионных тестов к строкам документов, содержащих тестовые случаи.
Если вы поместили тесты в модуль, модуль сам может быть программой запуска тестов. Если тест не работает, вы можете настроить свой прогонщик так, чтобы он повторно выполнял только неудачный тест, пока вы отлаживаете проблему. Вот минимальный пример такого прогонщика:
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")
Сноски