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 tests in __main__
8 tests in __main__.factorial
9 tests in 2 items.
9 passed and 0 failed.
Test passed.
$
Это всё, что вам нужно знать, чтобы начать продуктивно использовать doctest! Приступайте. В следующих разделах представлены полные сведения. Обратите внимание, что в стандартном наборе тестов и библиотеках Python имеется множество примеров doctest. Особенно полезные примеры можно найти в стандартном тестовом файле Lib/test/test_doctest.py.
26.3.1. Простое использование: Проверка примеров в строковых документациях
Самый простой способ начать использовать 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(). Вы можете указать интерпретатору Python запустить модуль doctest напрямую из стандартной библиотеки и передать имя(а) модуля(ей) в командной строке:
python -m doctest -v example.py
Это импортирует example.py как отдельный модуль и выполнит testmod() на нём. Обратите внимание, что это может не сработать правильно, если файл является частью пакета и импортирует другие подмодули из этого пакета.
Для получения более подробной информации о testmod() см. раздел Основной API.
26.3.2. Простое использование: Проверка примеров в текстовом файле
Ещё одно простое применение doctest — тестирование интерактивных примеров в текстовом файле. Это можно сделать с помощью функции testfile():
import doctest
doctest.testfile("example.txt")
Этот короткий скрипт выполняет и проверяет любые интерактивные примеры Python, содержащиеся в файле example.txt. Содержимое файла обрабатывается как один гигантский docstring; файл не обязан содержать программу 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(). Вы можете указать интерпретатору Python запустить модуль doctest напрямую из стандартной библиотеки и передать имя(а) файла(ей) в командной строке:
python -m doctest -v example.txt
Поскольку имя файла не оканчивается на .py, doctest понимает, что его нужно запускать с testfile(), а не с testmod().
Для получения более подробной информации о testfile() см. раздел Основной API.
26.3.3. Как это работает
Этот раздел подробно рассматривает, как работает doctest: какие строковые документации он рассматривает, как он находит интерактивные примеры, какой контекст выполнения он использует, как он обрабатывает исключения и как флаги опций можно использовать для управления его поведением. Эта информация необходима для написания примеров doctest; информацию о фактическом запуске doctest для этих примеров см. в следующих разделах.
26.3.3.1. Какие строковые документации рассматриваются?
Рассматриваются строковая документация модуля, а также все строковые документации функций, классов и методов. Объекты, импортированные в модуль, не рассматриваются.
Кроме того, если M.__test__ существует и «истина», то это должен быть словарь, и каждое значение сопоставляет (строку) имя с объектом функции, классом или строкой. Строковые документации функций и классов, найденные в M.__test__, рассматриваются, а строки рассматриваются так, как будто они являются строковыми документациями. В выводе ключ K в M.__test__ появляется с именем
<name of M>.__test__.K
Любые найденные классы аналогичным образом проверяются рекурсивно, для тестирования строковых документаций содержащихся в них методов и вложенных классов.
Примечание реализации CPython: До версии 3.4 модули расширений, написанные на C, не проверялись doctest полностью.
26.3.3.2. Как распознаются примеры в строковых документациях?
В большинстве случаев копия и вставка интерактивного сеанса консоли работает нормально, но 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или директива установлены. В качестве альтернативы тест можно переписать для захвата вывода и сравнения его с ожидаемым значением в рамках теста. Такое обращение с табуляциями в исходном коде было получено путем проб и ошибок и оказалось наименее подверженным ошибкам способом обработки табуляций. Можно использовать другой алгоритм обработки табуляций, написав пользовательский класс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и столько же ведущих пробельных символов удаляется из ожидаемого вывода, сколько было в начальной
'>>> 'строке, которая начала пример.
26.3.3.3. Какой контекст выполнения?
По умолчанию каждый раз, когда doctest находит строку документации для тестирования, он использует поверхностную копию глобальных переменных M, чтобы выполнение тестов не изменяло реальные глобальные переменные модуля и чтобы один тест в M не оставил следов, которые случайно позволили бы другому тесту работать. Это означает, что примеры могут свободно использовать любые имена, определенные на верхнем уровне в M, и имена, определенные ранее в строке документации, которая выполняется. Примеры не могут видеть имена, определенные в других строках документации.
Вы можете принудительно использовать свой собственный словарь в качестве контекста выполнения, передав globs=your_dict в testmod() или testfile().
26.3.3.4. Что насчёт исключений?
Без проблем, при условии, что трассировка стека является единственным выводом, производимым примером: просто вставьте трассировку. 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. Стек трассировки обычно опускается или копируется дословно из интерактивной сессии.
За стеком трассировки следует наиболее интересная часть: строка(и), содержащая тип и подробности исключения. Это обычно последняя строка трассировки стека, но может распространяться на несколько строк, если у исключения есть многострочный detail:
>>> 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
Обратите внимание, что трассировки обрабатываются очень специальным образом. В частности, в переписанном примере использование ... не зависит от опции ELLIPSIS doctest. Эллипсис в этом примере можно опустить, или его можно заменить тремя (или тремя сотнями) запятыми или цифрами, или отформатированным текстом из скетча Монти Пайтона.
Некоторые детали, которые стоит прочитать один раз, но не нужно запоминать:
- Doctest не может угадать, получен ли ожидаемый вывод из трассировки стека исключения или из обычного вывода. Поэтому, например, пример, который ожидает
ValueError: 42 is prime, пройдёт, независимо от того, возбуждено лиValueErrorили пример просто выводит текст трассировки. На практике обычный вывод редко начинается с строки заголовка трассировки, поэтому это не создаёт реальных проблем. - Каждая строка стека трассировки (если присутствует) должна быть отступа с большими значениями, чем первая строка примера, или начинаться с неалфавитно-цифрового символа. Первая строка, следующая за заголовком трассировки стека, с тем же отступом и начинающаяся с алфавитно-цифрового символа, считается началом деталей исключения. Разумеется, это делается правильно для реальных трассировок стеков.
- Когда указана опция doctest
IGNORE_EXCEPTION_DETAIL, всё, что следует слева от двоеточия и любой информации о модуле в имени исключения, игнорируется. - Интерактивная оболочка опускает строку заголовка трассировки стека для некоторых
SyntaxError. Но doctest использует строку заголовка трассировки стека, чтобы отличить исключения от неисключений. Поэтому в редких случаях, когда вам нужно протестироватьSyntaxError, которые опускают строку заголовка трассировки стека, вам придётся вручную добавить строку заголовка трассировки стека в ваш тестовый пример.
-
Для некоторых
SyntaxError, Python отображает позицию символа ошибки синтаксиса, используя маркер^:>>> 1 1 File "<stdin>", line 1 1 1 ^ SyntaxError: invalid syntaxПоскольку строки, показывающие положение ошибки, идут перед типом и подробностями исключения, они не проверяются doctest. Например, следующий тест пройдёт, даже если он помещает маркер
^в неправильном месте:>>> 1 1 File "<stdin>", line 1 1 1 ^ SyntaxError: invalid syntax
26.3.3.5. Флаги опций
Ряд флагов опций управляют различными аспектами поведения doctest. Символьные имена флагов предоставляются в качестве констант модуля, которые могут быть побитово объединены и переданы в различные функции. Имена также могут использоваться в директивах doctest и могут быть переданы в интерфейс командной строки doctest через опцию -o.
New in version 3.4: Опция командной строки -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 -
При указании, пример, ожидающий исключение, проходит, если возникает исключение ожидаемого типа, даже если детали исключения не совпадают. Например, пример, ожидающий
ValueError: 42, пройдет, если фактически возникшее исключение —ValueError: 3*14, но потерпит неудачу, например, если возникнетTypeError.Он также проигнорирует имя модуля, используемое в отчётах doctest в Python 3. Следовательно, оба варианта будут работать с указанным флагом независимо от того, выполняется ли тест под Python 2.7 или Python 3.2 (или более поздними версиями):
>>> raise CustomError('message') Traceback (most recent call last): CustomError: message >>> raise CustomError('message') Traceback (most recent call last): my_module.CustomError: messageОбратите внимание, что
ELLIPSISтакже можно использовать для игнорирования деталей сообщения об исключении, но такой тест всё же может потерпеть неудачу в зависимости от того, будут ли детали модуля напечатаны как часть имени исключения. ИспользованиеIGNORE_EXCEPTION_DETAILи деталей из Python 2.3 также является единственным ясным способом написать doctest, который не обращает внимания на детали исключения, но при этом продолжает проходить под Python 2.3 или более ранними версиями (эти версии не поддерживают директивы doctest и игнорируют их как нерелевантные комментарии). Например:>>> (1, 2)[3] = 'moo' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: object doesn't support item assignment
проходит под Python 2.3 и более поздними версиями Python с указанным флагом, хотя детали изменились в Python 2.4 на «не» вместо «не».
Изменено в версии 3.2:
IGNORE_EXCEPTION_DETAILтеперь также игнорирует любую информацию, относящуюся к модулю, содержащему исключение, которое тестируется.
-
doctest.SKIP -
При указании пример вообще не выполняется. Это может быть полезно в контекстах, где примеры doctest служат как документацией, так и тестовыми случаями, и пример должен быть включён для целей документации, но не должен проверяться. Например, вывод примера может быть случайным; или пример может зависеть от ресурсов, которые недоступны драйверу теста.
Флаг SKIP также может использоваться для временного «комментирования» примеров.
-
doctest.COMPARISON_FLAGS -
Битовая маска, объединяющая все флаги сравнения выше.
Вторая группа опций управляет тем, как сообщаются о сбоях тестов:
-
doctest.REPORT_UDIFF -
При указании сбои, которые включают ожидаемый и фактический многострочные выводы, отображаются с помощью унифицированной разницы.
-
doctest.REPORT_CDIFF -
При указании сбои, которые включают ожидаемый и фактический многострочные выводы, будут отображаться с помощью контекстной разницы.
-
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.Новое в версии 3.4.
-
doctest.REPORTING_FLAGS -
Битовая маска, объединяющая все флаги отчёта выше.
Также есть способ зарегистрировать новые имена флагов опций, хотя это не полезно, если вы не планируете расширять doctest внутренности путём наследования:
-
doctest.register_optionflag(name) -
Создаёт новый флаг опции с заданным именем и возвращает целое значение нового флага.
register_optionflag()можно использовать при наследовании отOutputCheckerилиDocTestRunnerдля создания новых опций, которые поддерживаются вашими подклассами.register_optionflag()всегда должен вызываться с помощью следующего выражения:MY_FLAG = register_optionflag('MY_FLAG')
26.3.3.6. Директивы
Директивы 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))) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
Без директивы он потерпел бы неудачу, как потому, что фактический вывод не имеет двух пробелов перед элементами однозначного списка, так и потому, что фактический вывод находится в одной строке. Этот тест также проходит и также требует директивы, чтобы это произошло:
>>> print(list(range(20))) [0, 1, ..., 18, 19]
Можно использовать несколько директив в одной физической строке, разделённые запятыми:
>>> print(list(range(20))) [0, 1, ..., 18, 19]
Если для одного примера используются несколько комментариев директивы, они объединяются:
>>> print(list(range(20))) ... [0, 1, ..., 18, 19]
Как показывает предыдущий пример, вы можете добавить ... строки в свой пример, содержащие только директивы. Это может быть полезно, когда пример слишком длинный для того, чтобы директива удобно поместилась в той же строке:
>>> print(list(range(5)) + list(range(10, 20)) + list(range(30, 40))) ... [0, ..., 4, 10, ..., 19, 30, ..., 39]
Обратите внимание, что поскольку все опции по умолчанию отключены, и директивы применяются только к примеру, в котором они появляются, включение опций (через + в директиве) обычно является единственным осмысленным выбором. Однако флаги опций также могут передаваться функциям, которые запускают doctests, устанавливая разные значения по умолчанию. В таких случаях отключение опции с помощью - в директиве может быть полезно.
26.3.3.7. Предупреждения
doctest серьёзно относится к требованию точного соответствия в ожидаемом выводе. Если даже один символ не совпадает, тест терпит неудачу. Это, вероятно, вас удивит несколько раз, когда вы узнаете точно, что Python делает и не гарантирует относительно вывода. Например, при печати словаря Python не гарантирует, что пары «ключ-значение» будут напечатаны в определённом порядке, поэтому тест, подобный
>>> foo()
{"Hermione": "hippogryph", "Harry": "broomstick"}
уязвим! Одно из решений — сделать
>>> foo() == {"Hermione": "hippogryph", "Harry": "broomstick"}
True
Другое — сделать
>>> d = sorted(foo().items())
>>> d
[('Harry', 'broomstick'), ('Hermione', 'hippogryph')]
Есть и другие, но вы поняли суть.
Ещё одна плохая идея — печатать вещи, которые включают адрес объекта, например
>>> id(1.0) # certain to fail some of the time 7948648 >>> class C: pass >>> C() # the default repr() for instances embeds an address <__main__.C instance at 0x00AC18F0>
Директива ELLIPSIS даёт хорошее решение для последнего примера:
>>> C() <__main__.C instance 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 безопасны на всех платформах, и я часто использую примеры doctest, чтобы генерировать числа такого вида:
>>> 3./4 # utterly safe 0.75
Простые дроби также легче для понимания людьми, а это делает документацию лучше.
26.3.4. Базовый 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 выводит много информации, если он истинный, и только ошибки, если ложный; по умолчанию, или если
None, он истинный, только если'-v'находится вsys.argv.Необязательный аргумент report выводит сводку в конце, если истинный, иначе ничего не выводит. В режиме verbose сводка подробная, иначе краткая (на самом деле пустая, если все тесты прошли).
Необязательный аргумент optionflags (значение по умолчанию 0) использует побитовое ИЛИ флагов опций. Смотрите раздел Флаги опций.
Необязательный аргумент raise_on_error по умолчанию ложный. Если истинный, то при первой ошибке или неожиданной ошибке в примере генерируется исключение. Это позволяет выполнять пошаговую отладку ошибок. По умолчанию выполнение примеров продолжается.
Необязательный аргумент 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__, если он существует и неNone.m.__test__сопоставляет имена (строки) функциям, классам и строкам; строки документации функций и классов проверяются на наличие примеров; строки проверяются напрямую, как будто они были строками документации.Проверяются только строки документации объектов, принадлежащих модулю m.
Возвращает
(failure_count, test_count).Необязательный аргумент name задаёт имя модуля; по умолчанию, или если
None, используетсяm.__name__.Необязательный аргумент exclude_empty по умолчанию ложный. Если истинный, объекты, для которых не найдены doctest'ы, исключаются из рассмотрения. По умолчанию это «бэкворд-компатибл» хак, чтобы код, по-прежнему использующий
doctest.master.summarize()в сочетании сtestmod(), продолжал получать вывод для объектов без тестов. Аргумент exclude_empty в новом конструктореDocTestFinderпо умолчанию истинный.Необязательные аргументы 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 истинный, вывод генерируется даже если нет ошибок. По умолчанию вывод генерируется только в случае ошибки примера.
Необязательный аргумент compileflags задаёт набор флагов, которые должен использовать компилятор Python при выполнении примеров. По умолчанию, или если
None, флаги выводятся из набора будущих функций, найденных в globs.Необязательный аргумент optionflags работает так же, как для функции
testfile()выше.
26.3.5. 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. Функция setUp может получить доступ к глобальным переменным теста как к атрибуту 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, checker=None) -
Преобразовать тесты doctest для модуля в
unittest.TestSuite.Возвращаемая
unittest.TestSuiteдолжна выполняться фреймворком unittest и запускает каждый doctest в модуле. Если какой-либо из doctest завершается ошибкой, то синтезированный юнит-тест завершается неудачей, и генерируется исключениеfailureException, отображающее имя файла с тестом и (иногда приблизительный) номер строки.Необязательный аргумент module предоставляет модуль, который должен быть протестирован. Он может быть объектом модуля или (возможно, с точками) именем модуля. Если не указан, используется модуль, вызывающий эту функцию.
Необязательный аргумент globs — словарь, содержащий начальные глобальные переменные для тестов. Для каждого теста создаётся новая копия этого словаря. По умолчанию globs — новый пустой словарь.
Необязательный аргумент 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 принимает поразрядное ИЛИ флагов опций. См. раздел Флаги опций. Можно использовать только «флаги отчёта».
Это глобальная настройка модуля и влияет на все будущие доктесты, выполняемые модулем
unittest: методrunTest()объектаDocTestCaseпроверяет флаги опций, указанные для тестового случая, когда был создан экземплярDocTestCase. Если не были указаны флаги отчёта (что является типичным и ожидаемым случаем), флаги отчётаdoctestпо умолчанию поразрядно объединяются с флагами опций, и эти расширенные флаги опций передаются экземпляруDocTestRunner, созданному для выполнения доктеста. Если флаги отчёта были указаны при создании экземпляраDocTestCase, флаги отчётаdoctestигнорируются.Значение флагов отчёта
unittestдо вызова функции возвращается функцией.
26.3.6. Расширенный 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
+---------+
26.3.6.1. Объекты DocTest
-
class doctest.DocTest(examples, globs, name, filename, lineno, docstring) -
Коллекция примеров doctest, которые следует запускать в одном пространстве имён. Аргументы конструктора используются для инициализации атрибутов с одинаковыми именами.
Класс
DocTestопределяет следующие атрибуты. Они инициализируются конструктором и не должны изменяться напрямую.-
examples -
Список объектов
Example, кодирующих отдельные интерактивные примеры Python, которые должны выполняться в этом тесте.
-
globs -
Пространство имён (также известное как глобальные переменные), в котором должны выполняться примеры. Это словарь, сопоставляющий имена с значениями. Любые изменения в пространстве имён, внесённые примерами (например, присвоение новых переменных), будут отражены в
globsпосле выполнения теста.
-
name -
Строковое имя, идентифицирующее
DocTest. Обычно это имя объекта или файла, из которого был извлечён тест.
-
filename -
Имя файла, из которого был извлечён этот
DocTest; илиNoneесли имя файла неизвестно илиDocTestне был извлечён из файла.
-
lineno -
Номер строки в
filename, где начинается этотDocTest, илиNoneесли номер строки недоступен. Этот номер строки нулевой относительно начала файла.
-
docstring -
Строка, из которой был извлечён тест, или
Noneесли строка недоступна или тест не был извлечён из строки.
-
26.3.6.2. Объекты Example
-
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’soptionflags). По умолчанию опции не устанавливаются.
-
26.3.6.3. Объекты DocTestFinder
-
class doctest.DocTestFinder(verbose=False, parser=DocTestParser(), recurse=True, exclude_empty=True) -
Класс обработки, используемый для извлечения
DocTestов, относящихся к данному объекту, из его docstring и docstring содержащихся в нём объектов.DocTestы могут быть извлечены из модулей, классов, функций, методов, static методов, class методов и свойств.Необязательный аргумент verbose может использоваться для отображения объектов, просматриваемых поисковиком. По умолчанию он равен
False(без вывода).Необязательный аргумент parser задаёт объект
DocTestParser(или его замену), используемый для извлечения doctest из docstring.Если необязательный аргумент recurse равен false, то
DocTestFinder.find()будет рассматривать только заданный объект, а не какие-либо содержащиеся в нём объекты.Если необязательный аргумент exclude_empty равен false, то
DocTestFinder.find()будет включать тесты для объектов с пустыми docstring.DocTestFinderопределяет следующий метод:-
find(obj[, name][, module][, globs][, extraglobs]) -
Возвращает список
DocTestов, определённых docstring объекта obj или docstring любого содержащегося в нём объекта.Необязательный аргумент name задаёт имя объекта; это имя будет использовано для построения имён возвращаемых
DocTestов. Если name не указан, используетсяobj.__name__.Необязательный параметр module — это модуль, содержащий данный объект. Если модуль не указан или равен
None, то поисковик тестов попытается автоматически определить правильный модуль. Модуль объекта используется:- В качестве пространства имён по умолчанию, если globs не указан.
- Для предотвращения извлечения DocTest из объектов, импортированных из других модулей. (Содержащиеся объекты с модулями, отличными от module, игнорируются.)
- Для поиска имени файла, содержащего объект.
- Для помощи в поиске номера строки объекта в его файле.
Если module равен
False, попытка найти модуль не будет предпринята. Это неочевидно, используется в основном при тестировании самого doctest: если module равенFalse, или равенNone, но не может быть найден автоматически, то все объекты считаются принадлежащими (несуществующему) модулю, поэтому для всех содержащихся объектов будет (рекурсивно) проводиться поиск doctest.Глобальные переменные для каждого
DocTestформируются путём объединения globs и extraglobs (связывания в extraglobs переопределяют связывания в globs). Для каждогоDocTestсоздается новая неглубокая копия словаря глобальных переменных. Если globs не указан, он по умолчанию равен __dict__ модуля, если указан, или{}в противном случае. Если extraglobs не указан, он по умолчанию равен{}.
-
26.3.6.4. Объекты 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и строк. Номера строк для объектовExample— 0-основанные. Необязательный аргумент name — это имя, идентифицирующее данную строку, и используется только для сообщений об ошибках.
-
26.3.6.5. Объекты DocTestRunner
-
class doctest.DocTestRunner(checker=None, verbose=None, optionflags=0) -
Класс обработки, используемый для выполнения и проверки интерактивных примеров в
DocTest.Сравнение ожидаемого и фактического вывода выполняется с помощью
OutputChecker. Это сравнение можно настроить с помощью различных флагов параметров; см. раздел Флаги параметров для получения дополнительной информации. Если флагов параметров недостаточно, сравнение также можно настроить, передав подклассOutputCheckerв конструктор.Вывод результатов работы тестового исполнителя можно контролировать двумя способами. Во-первых, можно передать функцию вывода в
TestRunner.run(); эта функция будет вызываться со строками, которые должны быть отображены. По умолчанию используетсяsys.stdout.write. Если захват вывода недостаточен, вывод можно также настроить, создав подкласс DocTestRunner и переопределив методыreport_start(),report_success(),report_unexpected_exception()иreport_failure().Необязательный ключевой аргумент checker задаёт объект
OutputChecker(или его замену), который используется для сравнения ожидаемого вывода с фактическим выводом примеров doctest.Необязательный ключевой аргумент verbose управляет удобочитаемостью (verbose)
DocTestRunner. Если verbose равноTrue, то выводится информация о каждом примере во время его выполнения. Если verbose равноFalse, то выводится только информация об ошибках. Если verbose не указано или равноNone, то подробный вывод используется только если используется командная опция-v.Необязательный ключевой аргумент optionflags может быть использован для управления тем, как тестовый исполнитель сравнивает ожидаемый вывод с фактическим выводом и как он отображает ошибки. Подробнее см. раздел Флаги параметров.
DocTestParserопределяет следующие методы:-
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.Примеры выполняются в пространстве имен
test.globs. Если clear_globs имеет значение true (по умолчанию), то это пространство имен будет очищено после выполнения теста, чтобы помочь с garbage collection. Если вы хотите проверить пространство имен после завершения теста, используйте clear_globs=False.compileflags задаёт набор флагов, которые должны использоваться компилятором Python при выполнении примеров. Если не указано, то по умолчанию используется набор флагов future-import, применимых к globs.
Вывод каждого примера проверяется с помощью проверки вывода
DocTestRunner, а результаты форматируются методамиDocTestRunner.report_*().
-
summarize(verbose=None) -
Печатает сводку всех тестов, которые были запущены этим DocTestRunner, и возвращает кортеж
TestResults(failed, attempted).Необязательный аргумент verbose управляет степенью подробности сводки. Если уровень подробности не указан, используется уровень подробности
DocTestRunner.
-
26.3.6.6. Объекты 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.
-
26.3.7. Отладка
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(), передавая объект traceback из необработанного исключения. Если pm не указан или равен ложному значению, скрипт выполняется в отладчике с самого начала, передавая соответствующий вызовexec()кpdb.run().
-
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 -
Пример, который завершился неудачей.
-
DocTestFailure.got -
Фактический вывод примера.
-
exception doctest.UnexpectedException(test, example, exc_info) -
Исключение, генерируемое
DocTestRunnerдля сигнализации о том, что пример doctest вызвал непредвиденное исключение. Аргументы конструктора используются для инициализации атрибутов с одинаковыми именами.
UnexpectedException определяет следующие атрибуты:
-
UnexpectedException.test -
Объект
DocTest, который выполнялся, когда пример завершился неудачей.
-
UnexpectedException.example -
Пример, который завершился неудачей.
-
UnexpectedException.exc_info -
Кортеж, содержащий информацию о непредвиденном исключении, возвращаемый
sys.exc_info().
26.3.8. Soapbox
Как упоминалось во введении, doctest эволюционировал до трёх основных применений:
- Проверка примеров в строковых документах.
- Тестирование регрессии.
- Исполняемая документация/литеральное тестирование.
Эти применения имеют разные требования, и важно их различать. В частности, заполнение строковых документов запутанными тестовыми случаями делает документацию плохой.
При написании строкового документа тщательно выбирайте примеры в документе. В этом есть искусство, которое нужно освоить — на начальном этапе это может быть не естественно. Примеры должны добавлять реальную ценность документации. Хороший пример часто стоит многих слов. При внимательном выполнении примеры будут неоценимы для ваших пользователей и окупят потраченное время многократно в течение многих лет, когда вещи меняются. Я всё ещё поражаюсь, как часто один из моих doctest примеров перестаёт работать после «безобидного» изменения.
Доктест также является отличным инструментом для регрессионного тестирования, особенно если вы не экономите на пояснительном тексте. Объединив прозу и примеры, гораздо проще отслеживать, что именно тестируется и почему. Когда тест терпит неудачу, хорошая проза может значительно облегчить выяснение проблемы и того, как её исправить. Конечно, вы можете написать обширные комментарии в кодовом тестировании, но немногие программисты это делают. Многие обнаружили, что использование подхода doctest приводит к гораздо более понятным тестам. Возможно, это просто потому, что doctest делает написание прозы немного проще, чем написание кода, а написание комментариев в коде немного сложнее. Думаю, дело глубже: естественное отношение при написании теста на основе doctest заключается в том, что вы хотите объяснить тонкости вашего программного обеспечения и проиллюстрировать их примерами. В свою очередь, это естественным образом приводит к файлам тестов, которые начинаются с самых простых функций и логично переходят к усложнениям и граничным случаям. В результате получается связный рассказ, а не набор изолированных функций, которые тестируют изолированные части функциональности, как будто случайным образом. Это другое отношение, и оно даёт разные результаты, размывая границу между тестированием и объяснением.
Регрессионное тестирование лучше всего ограничивать выделенными объектами или файлами. Существует несколько вариантов организации тестов:
- Создавайте текстовые файлы, содержащие тестовые случаи в виде интерактивных примеров, и тестируйте файлы с помощью
testfile()илиDocFileSuite(). Это рекомендуется, хотя проще всего это сделать для новых проектов, разработанных с самого начала с использованием doctest. - Определите функции с именем
_regrtest_topic, которые состоят из одного docstring, содержащего тестовые случаи для указанных тем. Эти функции могут быть включены в тот же файл, что и модуль, или выделены в отдельный файл теста. - Определите
__test__словарь, отображающий темы регрессионного тестирования на docstring, содержащие тестовые случаи.
Когда вы разместили свои тесты в модуле, сам модуль может быть исполняемой средой тестов. Когда тест завершается неудачно, вы можете настроить исполняемую среду тестов на повторное выполнение только неудачного 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("{} failures out of {} tests".format(fail, total))
Примечания
-
1 -
Примеры, содержащие как ожидаемый вывод, так и исключение, не поддерживаются. Попытка угадать, где один заканчивается, а другой начинается, слишком подвержена ошибкам, и это также создаёт запутанные тесты.
© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.6/library/doctest.html