doctest
— Тестирование интерактивных примеров на Python¶
Модуль doctest
ищет куски текста, которые выглядят как
интерактивные сессии Python и затем выполняет эти сессии, чтобы проверить,
что они работают точно так же, как показано. Есть несколько стандартных
причин, чтобы использовать doctest:
- Для того, чтобы проверить актуальность строк документации, убедившись, что все интерактивные примеры работают именно так, как задокументировано.
- Чтобы организовать регрессионное тестирование, проверяя, что интерактивные примеры из тестового файла или тестового объекта работают как ожидается.
- Чтобы написать руководство для пакета, иллюстрированное примерами ввода-вывода. В зависимости от того, на что обращается внимание - на примеры или на пояснительный текст, это можно назвать либо “литературным тестированием”, либо “исполняемой документацией”.
Вот полный, но небольшой пример модуля:
"""
Это модуль-пример.
Этот модуль предоставляет одну функцию - factorial(). Например,
>>> factorial(5)
120
"""
def factorial(n):
"""Возвращает факториал числа n, которое является числом >= 0.
Если резульатат умещается в int, возвращается int.
Иначе возвращается long.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> [factorial(long(n)) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(30)
265252859812191058636308480000000L
>>> factorial(30L)
265252859812191058636308480000000L
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Можно вычислять факториал числа с десятичной частью, если она
равна 0:
>>> factorial(30.1)
Traceback (most recent call last):
...
ValueError: n must be exact integer
>>> factorial(30.0)
265252859812191058636308480000000L
Кроме того, число не должно быть слишком большим:
>>> 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: # перехватываем значения типа 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(long(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 и его библиотеках. Особенно полезные примеры можно найти
в стандартном тестовом файле
Lib/test/test_doctest.py
.
Простое использование: Проверка примеров в Docstrings¶
Самый простой способ начать использовать 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=True
в testmod()
, или
отключить его, передав verbose=False
. В любом из этих двух случаев,
sys.argv
не проверяется функцией testmod()
(так что наличие или отсутствие -v
ничего не меняет).
Начиная с Python 2.6 появилось сокращение для коммандной строки для запуска
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
. Содержимое файла должно соответствовать
одной большой строке документации; файл не должен содержать программы на Python!
Например, файл example.txt
может содержать следующее:
Модуль ``example``
======================
Использование ``factorial``
-------------------
Это пример текстового файла в формате reStructuredText. Во-первых, импортируем
``factorial`` из модуля ``example``:
>>> from example import factorial
Теперь используем его:
>>> 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.
Начиная с Python 2.6 появилось сокращение для коммандной строки для запуска
testfile()
. Вы можете указать интерпретатору Python запустить модуль doctest
напрямую из стандартной библиотеки и передать этому модулю имя/имена файла/файлов как аргументы:
python -m doctest -v example.txt
Так как имя файла не заканчивается на .py
, doctest
понимает, что должна быть
запущена testfile()
, а не testmod()
.
Более подробно testfile()
описана в разделе Базовый API.
Как это работает¶
Этот раздел детально объясняет, как работает doctest: на какие строки документации он смотрит, как он обнаруживает интерактивные примеры, какой контекст исполнения он использует, как обрабатывает исключения и какие опции можно использовать для управления его поведением. Это та информацию, которая Вам понадобится для написания доктестов; информацию о запуске доктестов на этих примерах смотрите выше.
Какие строки документации проверяются?¶
Ищутся строки документации модуля, всех функций, классов и методов. Объекты, импортируемые в модуль не исследуются.
Кроме того, если существует M.__test__
и он “истинен”, то он должен быть словарём,
и каждый элемент отображает строковое имя на объект функции, класса или строку.
Ищутся строки документации для этих функций и классов, а строки трактуются как строки
документации. В выводе ключ K
из
M.__test__
будет отображаться так
<имя M>.__test__.K
У всех найденных классов будет произведён рекурсивный поиск строк документации во вложеных методах и классах.
Changed in version 2.4: Концепция “private name” устарела и больше не документируется.
Как распознаются примеры из строк документации?¶
В большинстве случаев простое копирование-вставка из интерактивной консоли будет работать хорошо, но doctest не пытается точно эмулировать оболочку Python`а.
>>> # комментарии игнорируются
>>> x = 12
>>> x
12
>>> if x == 13:
... print "yes"
... else:
... print "no"
... print "NO"
... print "NO!!!"
...
no
NO
NO!!!
>>>
Весь ожидаемый вывод должен следовать непосредственно за последней строкой с кодом
(начинающейся с '>>> '
или '... '
), а вслед за выводом (если он есть)
должна идти строка с '>>> '
или пустая строка.
The fine print:
Ожидаемый вывод не может содержать строку из пробелов, так как такая строка означает конец ожидаемого вывода. Если вывод должен содержать пустую строку, поместите
<BLANKLINE>
в пример строки документации там, где должна быть эта строка.New in version 2.4: Был добавлен
<BLANKLINE>
; в предыдущих версиях не было никакой возможности использовать ожидаемый вывод с пустыми строками.Все символы табуляции заменяются пробелами, колонки содержат по 8 столбцов. Табуляция в выводе, полученном тестируемым кодом не изменяются. Поскольку все табуляции в примере вывода замещаются, значит, если вывод кода содержит знаки табуляции, единственный способ пройти тест - использовать опцию
NORMALIZE_WHITESPACE
или директиву. Или же, тест может быть переписан, чтобы он перехватывал вывод и сравнивал его с ожидаемым значением как часть теста. Такая обработка табуляции в исходном коде была получена методом проб и ошибок и зарекомендовала себя тоже глючным способом (This handling of tabs in the source was arrived at through trial and error, and has proven to be the least error prone way of handling them.) Возможно использовать другой алгоритм для обработки табуляции, если Вы напишете свой классDocTestParser
.Changed in version 2.4: Появилась замена табуляции на пробелы. В предыдущих версиях попытка сохранить табуляцию приводила к странным результатам.
Вывод в stdout захватывается, а вывод в stderr - нет (трассировка исключений захватывается по другому).
Если Вы продолжаете строку при помощи обратного слеша в интерактивной сессии или по какой-либо другой причине используете обратные слеши, Вы должны использовать “сырые” строки документации, которые сохранят ваши обратные слеши в первозданном виде:
>>> def f(x): ... r'''Backslashes in a raw docstring: m\n''' >>> print f.__doc__ Backslashes in a raw docstring: m\n
В противном случае, обратные слеши будут интерпретироваться как часть строки. Например,
\n
из примера выше, будет интерпретироваться как знак начала новой строки. Кроме того, Вы можете удвоить каждый бэкслеш (и не использовать “сырые” строки):>>> 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.0
и от ожидаемого вывода будет отрезано столько пробелов, сколько предшествует
'>>> '
в строке, начинающей пример.
Каков контекст выполнения?¶
По умолчанию, каждый раз, когда doctest
находит стоки документации, которые
надо протестировать, он использует shallow copy глобальных переменных модуля M
‘,
так что запуск теста не изменяет реальные значения переменных модуля, так что один тест
модуля M
не может оставить за собой “следы”, котоыре могут случайно привести
к выполнению другого теста. Это означает, что примеры могут спокойно использовать любые
имена, определённые на верхнем уровне модуля M
, и имена, определённые
ранее в этой строке документации. Примеры не могут видеть имена, определённые в
другом примере (строке документации).
Вы можете принудительно использовать свой собственный словарь как
контекст выполнения, передав его как аргумент
globs
в testmod()
или testfile()
.
Что по поводу исключений?¶
Никаких проблем, при условии, что трассировка - единственный вывод примера: просто вставьте трассировку. [1] Так как трассировка содержит детали, которые постоянно изменяются (например, путь к файлу, номер строки), то это тот случай, когда doctest прикладывает все усилия, чтобы гибко обработать эту ситуацию.
Вот простой пример:
>>> [1, 2, 3].remove(42)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ValueError: list.remove(x): x not in list
Этот пример будет успешен, если возникнет исключение ValueError
,
с текстом list.remove(x): x not in list
.
Ожидаемый вывод исключения должен начинаться с заголовка трассировки, который может быть одной из двух строк, находящимся на том же уровне отступа, что и первая строка примера:
Traceback (most recent call last):
Traceback (innermost last):
За заголовком трассировки может следовать стек трассировки, содержание которого игнорируется тестом. Обычно его опускают или копируют из интерактивной оболочки.
За стеком идёт наиболее интересная часть: строка(строки), содержащие тип исключения и информацию о нём. Обычно это самая последняя строка трассировки, но она может занимать и несколько строк, если информация об исключении содержит несколько строк:
>>> raise ValueError('multi\n line\ndetail')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ValueError: multi
line
detail
Последние три строчки (начиная с ValueError
) сравниваются с типом
исключения и информацией о нём, а всё остальное игнорируется.
Changed in version 2.4: Предыдущие версии не могли обрабатывать многострочную информацию об исключении.
Лучше всего считается опустить стек трассировки, если только он не добавляет важной информации к примеру. Так что последний пример лучше записать так:
>>> raise ValueError('multi\n line\ndetail')
Traceback (most recent call last):
...
ValueError: multi
line
detail
Обратите внимание, что трассировки трактуются очень по особенному. В частности,
в переписанном выше примере использование ...
не зависит от опции
ELLIPSIS
doctest’а. Вместо многоточия может быть любое количество
других символов и даже транскрипт скетча Монти Пайтона.
Вот ещё несколько деталей, которые Вы должны прочитать но не обязаны помнить:
Doctest не может догадаться откуда берётся вывод - из трассировки исключения или от инструкции print. Таким образом, пример, который ожидает результат
ValueError: 42 is prime
будет пройден вне зависимости от того, на самом ли деле возникает исключениеValueError
или просто выводится такой текст. На практике реальный вывод редко начинается с заголовка трассировки, так что это скорее всего не создат проблемы.Каждая строка стека трассировки (если есть) должна иметь отступ больше, чем первая строка примера или начинаться с не алфавитно-цифрового символа. Первая строка за заголовком трассировки с тем же отступом и начинающаяся с алфавитно-цифрового символа считается началом информации об исключении. Что соответствует реальной трассировке.
Если определена опция
IGNORE_EXCEPTION_DETAIL
doctest`а, всё, что находится за самым левым двоеточием и вся информация о модуле в имени исключения игнорируется.Интерактивная оболочка опускает заголовок трассировки для некоторых
SyntaxError
. Однако doctest использует заголовок трассировки для однозначного определения исполняемой части документации от не исполняемой. Так что в тех редких случаях, когда Вам надо протестироватьSyntaxError
, у которого опущен заголовок трассировки, Вам нужно в ручную добавить этот заголовок в ваш тестовый пример.Для некоторых
SyntaxError
, Python отображает позицию символа на котором возникла ошибка при помощи маркера^
:>>> 1 1 File "<stdin>", line 1 1 1 ^ SyntaxError: invalid syntax
Так как эта строка находится до обозначения типа исключения, она игнорируется. Например, следующий тест тоже будет считаться пройденным успешно, хотя маркер
^
находится в неверной позиции:>>> 1 1 File "<stdin>", line 1 1 1 ^ SyntaxError: invalid syntax
Опции/флаги¶
Есть некоторое количество опций, которые определяют поведение doctest’а. Символические имена для этих опций определены как константы модуля, которые можно совмещать при помощи or и передавать различным функциям. Этим имена так же можно использовать в директивах doctest`а.
Первая группа опций определяет семантику теста, контролирует то, как doctest определяет совпадает ли полученный вывод с ожидаемым или нет:
-
doctest.
DONT_ACCEPT_TRUE_FOR_1
¶ По умолчанию, если ожидаемый вывод содержит только
1
, то результат, содержащий только1
или простоTrue
будет считаться верным, аналогично0
иFalse
. Когда используетсяDONT_ACCEPT_TRUE_FOR_1
такие замены не будут работать. Поведение по умолчанию необходимо потому, что Python изменяет возвращаемый тип многих функций с числового на логический и такое поведение doctest`a будет работать в этих ситуациях. Эта опция скорее всего будет убрана, но не в ближайшее время.
-
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`a в Python 3. Следовательно оба варианта при использовании этого флага будут работать в любой версии Python:
>>> 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 - единственный ясный способ написать тест, который не обращает внимание на информацию об исключении и выполняется под Python 2.3 и раньше (эти релизы не поддреживают директивы doctest`a и игнорируют их как комментарии). Например:>>> (1, 2)[3] = 'moo' Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: object doesn't support item assignment
будет выполнен в Python 2.3 и более поздних версиях с этим флагом, хотя информация об исключении изменилась начиная с версии 2.4 и будет содержать “does not” вместо “doesn’t”.
Changed in version 2.7:
IGNORE_EXCEPTION_DETAIL
теперь игнорирует любую информацию исключения, относящуюся к модулю.
-
doctest.
SKIP
¶ Когда используется, вообще не запускает пример. Это может быть полезно в контексте когда примеры используются и как документация и как тесты, и пример должен быть включён как часть документации, но не должен проверяться. Например, вывод примера может быть случайным, или пример может зависеть от того, что не доступно во время теста.
Флаг SKIP так же можно использовать чтобы временно “закомментировать” пример.
New in version 2.5.
-
doctest.
COMPARISON_FLAGS
¶ Битовая маска, сочетающая все флаги сравнения, приведённые выше.
Вторая группа опций определяет то, как будет сообщено о том, что тест не пройден:
-
doctest.
REPORT_UDIFF
¶ Когда используется, ошибки, содержащие многострочный реальный и ожидаемый вывод отображаются при помощи unified diff.
-
doctest.
REPORT_CDIFF
¶ Когда используется, ошибки, содержащие многострочный реальный и ожидаемый вывод отображаются при помощи context diff.
-
doctest.
REPORT_NDIFF
¶ Когда используется, разница вычисляется при помощи
difflib.Differ
, используя тот же алгоритм, что и популярная утилитаndiff.py
. Это единственный метод, который отмечает отличия как внутри строк, так и между строк. Например, если строка ожидаемого вывода содержит цифру1
, а реальный вывод содержит символl
, то строка появится с меткой, указывающий на несовпадающую позицию символа в строке.
-
doctest.
REPORT_ONLY_FIRST_FAILURE
¶ Если используется, показывает только первый неудачный тест и подавляет вывод всех остальных тестов. Это позволяет избежать сообщений об ошибках, которые возникают из-за ошибок в более ранних тестах. Но это же может скрыть не прошедшие тесты, которые были провалены вне зависимости от первой ошибки. Когда используется
REPORT_ONLY_FIRST_FAILURE
, остальные примеры выполняются и общее число ошибок тоже будет выведено, но сами ошибки будут скрыты.
-
doctest.
REPORTING_FLAGS
¶ Битовая маска, включающая все флаги отчётов выше.
New in version 2.4: Были добавлены константы
DONT_ACCEPT_BLANKLINE
, NORMALIZE_WHITESPACE
,
ELLIPSIS
, IGNORE_EXCEPTION_DETAIL
, REPORT_UDIFF
,
REPORT_CDIFF
, REPORT_NDIFF
,
REPORT_ONLY_FIRST_FAILURE
, COMPARISON_FLAGS
and
REPORTING_FLAGS
.
Кроме того, можно зарегистрировать новый флаг, хотя это и бесполезно
если только Вы не планируете расширять doctest
через подклассы:
-
doctest.
register_optionflag
(name)[source]¶ Создаёт новый флаг с заданным именем и возвращает его числовое значение.
register_optionflag()
может быть использована при создании дочерних классовOutputChecker
илиDocTestRunner
, чтобы добавить новые возможности, поддерживаемые вашим классом.register_optionflag()
всегда должна вызываться следующим образом:MY_FLAG = register_optionflag('MY_FLAG')
New in version 2.4.
Директивы¶
Директивы doctest`a можно использовать, чтобы изменить флаги для конкретных примеров. Директивами являются специальные комментарии, которые идут за исходным кодом примера:
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’а только для одного примера.
Используйте +
чтобы включить нужное поведение или -
, чтобы его отключить.
Например, этот текст успешно выполняется:
>>> print range(20)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
Без директивы, он не прошёл бы потому что реальный вывод не содержит два пробела элементами списка, содержащими одну цифру,и потому что он располагается на одной строке. Следующий тест тоже будет успешным, и ему тоже нужна для этого директива:
>>> print range(20)
[0, 1, ..., 18, 19]
Можно использовать несколько директив в одной строке, разделив их запятыми:
>>> print range(20)
[0, 1, ..., 18, 19]
Если несколько директив используется для одного примера, то они комбинируются:
>>> print range(20)
...
[0, 1, ..., 18, 19]
Как показывает предыдущий пример, Вы можете добавить строку ...
, которая содержит
только директивы, в ваш пример. Это может быть полезным в случае, если пример слишком
длинный, чтобы директивы уместились на той же строке:
>>> print range(5) + range(10,20) + range(30,40) + range(50,60)
...
[0, ..., 4, 10, ..., 19, 30, ..., 39, 50, ..., 59]
Обратите внимание, что так как по умолчанию все опции отключены и директивы
применяются только к тому примеру, где они указаны, полезным бывает лишь включение
опции (при помощи +
в директиве). Однако флаги можно передать и
функциям, которые запускают тесты, чтобы изменить их поведение. В этих
случая может быть полезным отключение опции при помощи -
.
New in version 2.4: Была добавлена поддержка директив doctest`a.
Предупреждения¶
doctest
очень строго относится к тому, чтобы реальный вывод совпадал
с ожидаемым. Если не совпадает даже один символ - тест всё равно будет провален.
Возможно, это приведёт Вас к некоторым открытиям в том, что гарантируется, а что
нет в выводе Python. Например когда Вы выводите словарь, Python не гарантирует,
что пары ключ-значение будут напечатаны в каком-то определённом порядке, так что
тест вроде:
>>> foo()
{"Hermione": "hippogryph", "Harry": "broomstick"}
опасен! Один из способов обойти это:
>>> foo() == {"Hermione": "hippogryph", "Harry": "broomstick"}
True
Другой:
>>> d = foo().items()
>>> d.sort()
>>> d
[('Harry', 'broomstick'), ('Hermione', 'hippogryph')]
Есть и другие, но Вы поняли.
Ещё одна плохая идея - сравнивать информацию об адресе объекта, как:
>>> id(1.0) # будет периодически неуспешным
7948648
>>> class C: pass
>>> C() # по умолчанию repr() отобразит адрес объекта
<__main__.C instance at 0x00AC18F0>
Директива ELLIPSIS
поможет Вам с последним примером:
>>> C()
<__main__.C instance at 0x...>
Числа с плавающей точкиой тоже являются причиной небольших отличий на разных платформах, так как Python обращается к библиотеке С платформы для форматирования этих чисел, а эти библиотеки очень отличаются в этом вопросе:
>>> 1./7 # опасно
0.14285714285714285
>>> print 1./7 # безопаснее
0.142857142857
>>> print round(1./7, 6) # ещё лучше
0.142857
Числа в виде I/2.**J
безопасны на всех платформах и я часто придумывают
тесты чтобы получитьь число в такой форме:
>>> 3./4 # очень безопасно
0.75
Кроме того, людям проще воспринимать простые дроби и это поможет Вам лучше написать документацию.
Базовый API¶
Функции testmod()
и testfile()
предоставляют простой интерфейс к
doctest, которого должно быть достаточно для большинства случаев. Для менее формального
знакомства с этими функциями смотрите разделы Простое использование: Проверка примеров в Docstrings
и Простое использование: Проверка примеров в текстовом файле.
-
doctest.
testfile
(filename[, module_relative][, name][, package][, globs][, verbose][, report][, optionflags][, extraglobs][, raise_on_error][, parser][, encoding])[source]¶ Все аргументы, кроме filename не являются обязательными и должны быть указаны как именованные аргументы.
Запускает тесты в файле filename. Возвращает
(failure_count, test_count)
.Не обязательный аргумент module_relative определяет, как должно быть интерперетировано имя файла:
- Если module_relative
True
(по умолчанию), тогда filename определяет OС-независимый относительный путь. По умолчанию, путь берётся от каталога вызывающего модуля, но если определён аргумент 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
, никаких дополнительных значений не используется. Это ещё одна возможность настройки теста. Например, тест может быть написан для базового класса, используя имя класса по умолчанию, а затем его можно использовать для любого другого количества подклассов, просто передав словарь extraglobs, который отображает имя по умолчанию на тот подкласс, который надо проверить.Не обязательный аргумент verbose приводит к выводу кучи информации, если он истинен, или выводятся только сообщения об ошибках, если ложен, не задан или равен
None
, он будет истинен только если'-v'
присутствует вsys.argv
.Не обязательный аргумент report выводит итог всех тестов, если истинен, иначе по завершению тестов не будет напечатано ничего. В “словоохотливом” режиме итог будет детальным, иначе - очень коротким (или пустым, если все тесты пройдены).
Не обязательный аргумент optionflags складывает все остальные флаги. Смотрите раздел Опции/флаги.
Не обязательный аргумент raise_on_error по умолчанию ложный. Если он истинный, то вызывается исключение при первой ошибке в тесте или неожиданном исключении. Это позволяет посмертно отладить ошибки. Поведение по умолчанию - продолжать выполнение тестов.
Не обязательный аргумент parser определяет
DocTestParser
(или подкласс), который будет использован для извлечения тестов из файлов. По умолчанию - обычный парсер (то есть,DocTestParser()
).Не обязательный аргумент encoding определяет кодировку, которая должна быть использована для перобразования файла в юникод.
New in version 2.4.
Changed in version 2.5: Был добавлен парамент encoding.
- Если module_relative
-
doctest.
testmod
([m][, name][, globs][, verbose][, report][, optionflags][, extraglobs][, raise_on_error][, exclude_empty])[source]¶ Все аргументы не обязательные, и все, за исключением m, должны передаваться как именованные аргументы.
Проверяет примеры в строках документации в функциях и классах, достигнутых из модуля m (или модуля
__main__
, если m не указан илиNone
), начиная сm.__doc__
.Кроме того, проверяет примеры, доступные из словаря
m.__test__
, если он существует и не равенNone
.m.__test__
отражает имена (строки) на функции, классы или строки; в функциях и классах ищутся тесты, строки рассматриваются напрямую как строки документации.Рассматриваются только строки документации, принадлежащие объектам из модуля m.
Возвращает
(failure_count, test_count)
.Не обязательный аргумент name содержит имя модуля; по умолчанию, или если равен
None
, используетсяm.__name__
.Не обязательный аргумент exclude_empty по умолчанию ложный. Если истинен, то объект, для которого не найдены строки документации исключается из рассмотрения. Значение по умолчанию является “костылём” для совместимости, так что код, использующий
doctest.master.summarize()
в месте сtestmod()
продолжает создавать вывод для объектов без тестов. Для нового классаDocTestFinder
аргумент exclude_empty по умолчанию истинен.Не обязательные аргументы extraglobs, verbose, report, optionflags, raise_on_error и globs аналогичны этим же аргументам для функции
testfile()
, за исключением того, что globs по умолчанию равенm.__dict__
.Changed in version 2.3: Был добавлен парамент optionflags.
Changed in version 2.4: Были добавлены параметры extraglobs, raise_on_error и exclude_empty.
Changed in version 2.5: Не обязательный аргумент isprivate, не рекомендуемый с 2.4, был удалён.
Кроме того, есть ещё функция для запуска doctest`ов, ассоциированных с отедльным объектом. Эта функция присутствует для обратной совместимости. Запрещать её не планируется, но она используется крайне редко:
-
doctest.
run_docstring_examples
(f, globs[, verbose][, name][, compileflags][, optionflags])[source]¶ Запускает тесты, ассоциированные с объектом f; например, f может быть модулем, функцией или классом.
В качестве контекста выполнения используется поверхностная копия аргумента globs.
Не обязательный аргумент name используется в сообщениях об ошибках и по умолчанию равно
"NoName"
.Если не обязательный аргумент verbose истинен, вывод будет, даже если никаких ошибок не было. По умолчанию вывод будет только если в тестах возникают ошибки.
Не обязательный аргумент compileflags даёт набор флагов, которые должны использоваться компилятором Python для выполнения тестов. По умолчанию, или если равен
None
, флаги определяются в соответствии с набором будущих возможностей, обнаруженных в globs (flags are deduced corresponding to the set of future features found in globs).Не обязательный аргумент optionflags работает так же, как в функции
testfile()
.
Unittest API¶
По мере того, как ваша коллекция модулей с doctest`ами будет расти, Вы
захотите получить возможность запускать все эти тесты систематически. До
Python 2.4, doctest
имел плохо документированный класс
Tester
, который предоставлял устаревший способ скомбинировать
тесты из нескольких модулей. Tester
был слаб и более серьёзные
фреймворки для тестирования построине на модуле unittest
, который
даёт более гибкие способы скомбинировать тесты из нескольких источников.
Так, в Python 2.4, Tester
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][, package][, setUp]
[, tearDown][, globs][, optionflags][, parser][, encoding])
Преобразует тест doctest из одного и более текстового файла в
unittest.TestSuite
.Возвращаемый
unittest.TestSuite
должен быть запущен фреймворком unittest и выполняет интерактивные примеры в каждом файле. Если тест в каком-либо файле не проходит, тогда полученный unit test провален и возбуждаетсяfailureException
, показывающее имя файла, содержащее тест и (иногда) номер строки.Принимает один или более путей (в виде строк) к текстовым файлам, которые надо проверить.
Опции могут быть указаны как именованные аргументы:
Не обязательный аргумент module_relative определяет то, как имя файла в paths должно интерпретироваться:
- Если module_relative
True
(по умолчанию), тогда filename определяет OС-независимый относительный путь. По умолчанию, путь берётся от каталога вызывающего модуля, но если определён аргумент package, тогда путь берётся относительно этого пакета. Чтобы обеспечить независимость от ОС filename должно использовать в строке пути символ/
, и он может быть не абсолютным (то есть, не начинаться с/
).- Если module_relative
False
, тогда filename определяет зависящий от ОС путь. Он может быть абсолютным или относительным; относительный путь разрешается относительно текущего рабочего каталога.Не обязательный аргумент package является пакетом Python или именем пакета Python, чей каталог надо использовать как базовый каталог для зависящих от модуля имён файлов в paths. Если пакет не определён, то используется каталог вызывающего модуля. Ошибкой является указывать package, если module_relative
False
.Не обязательный аргумент setUp определяет настроечную функцию для каждого набора тестов. Она вызывается перед запуском тестов в каждом файле. Функция setUp будет передана объекту
DocTest
. Эта функция имеет доступ к глобальным переменным теста через атрибут globs выполняемого теста.Не обязательный аргумент tearDown определяет “очищающую” функцию для набора тестов. Она вызывается после запуска тестов в каждом файле. Функция tearDown будет передана объекту
DocTest
. Эта функция имеет доступ к глобальным переменным теста через атрибут globs выполняемого теста.Не обязательный аргумент globs является словарём, содержащим начальные глобальные значения переменных для теста. Новая копия этого словаря создаётся для каждого теста. По умолчанию globs является пустым словарём.
Не обязательный аргумент optionflags определяет опции по умолчанию для теста, создаваемый при помощи “или” для отдельных опций. Смотрите раздел Опции/флаги. Смотрите ниже описание функции
set_unittest_reportflags()
, где описан лучший способ задания опций оповещения.Не обязательный аргумент parser определяет
DocTestParser
(или подкласс), который должен быть использован для извлечения тестов из файлов. По умолчанию это обычный парсер (то есть,DocTestParser()
).Не обязательный аргумент encoding определяет кодировку, которая должна быть использована для преобразования файла в юникод.
New in version 2.4.
Changed in version 2.5: Был добавлен
__file__
к глобальным переменным, предоставляемым doctest`ам, загружаемым из текстового файла при помощиDocFileSuite()
.Changed in version 2.5: Был добавлен параметр encoding.
Note
В отличие от
testmod()
иDocTestFinder
, эта функция возбуждает исключениеValueError
, если module не содержит строк документации. Вы можете избежать этого исключения, передав экземплярDocTestFinder
как аргумент test_finder с именованным аргументом exclude_empty установленным вFalse
:>>> finder = doctest.DocTestFinder(exclude_empty=False) >>> suite = doctest.DocTestSuite(test_finder=finder)
-
DocTestSuite([module][, globs][, extraglobs][, test_finder]
[, setUp][, tearDown][, checker])
Преобразует тесты для модуля в
unittest.TestSuite
.Возаращаемый
unittest.TestSuite
должен быть запущен под фреймворком unittest и он выполнит каждый тест в модуле. Если какой-либо тест не проходит, тогда синтезированный тест тоже не пройдёт и будет вызвано ислкючениеfailureException
, которое сообщит имя файла, содержащего тест и (иногда приблизительно) номер строки.Не обязательный аргумент module предоставляет модуль, который нужно протестировать. Это может быть как объектом модуля, так и имя модуля (даже с точками). Если не определено, используется модуль, вызывашинй эту функцию.
Не обязательный аргумент globs является словарём, содержащим начальные глобальные значения переменных для теста. Новая копия этого словаря создаётся для каждого теста. По умолчанию globs является пустым словарём.
Не обязательный аргумент extraglobs определяет дополнительные глобальные переменные, которые сливаются с globs. По умолчанию никаких дополнитеьльных переменных не используется.
Не обязательный аргумент test_finder является объектом
DocTestFinder
(или drop-in replacement), который будет использован для извлечения теста из модуля.Не обязательные аргументы setUp, tearDown, и optionflags аналогичны этим аргументам в функции
DocFileSuite()
.New in version 2.3.
Changed in version 2.4: Были добавлены параметры globs, extraglobs, test_finder, setUp, tearDown и optionflags; эта функция теперь использует тот же самый механизм поиска, что и
testmod()
.
Под “капотом” 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)[source]¶ Задаёт флаги
doctest
для оповещения.Аргумент flags соеденияет при помощи “или” флаги опций. Смотрите раздел Опции/флаги. Можно использовать только флаги оповещения.
Эта настройка глобальна для всего модуля и влияет на все следующие тесты, которые будут запущены модулем
unittest
: методrunTest()
классаDocTestCase
смотрит на флаги опций, определённые для набора тестов, когда создаётся экземплярDocTestCase
. Если флаги оповещений не были определены (что является обычным случаем), флаги оповещенияdoctest
‘а дляunittest
складываются при помощи “или” в флаги опций и они передаются в создаваемый экземплярDocTestRunner
, который будет запускать тесты. Если флаги были определены при создании классаDocTestCase
, флаги оповещенияdoctest
‘а дляunittest
игнорируются.Значение флагов оповещения
unittest
, которое оно было до вызова функции, возвращается функцией.New in version 2.4.
Продвинутый API¶
Базовый API является просто оболочкой, позволяющей проще использовать doctest. Он достаточно гибкий и должен удволетворять большую часть потребностей пользователей. Однако, если Вам нужен более детальный контроль над тестированием или Вы хотите расширить возможности doctest’а, тогда Вы должны использовать продвинутый API.
Продвинутый API работает вокруг двух классов-контейнеров, которые используются для хранения интерактивных примеров, извлечённых из строк документации:
Example
: одиночное выражение Python`а, в паре с его ожидаемым результатом.DocTest
: наборExample
ов, обычно извлечённых из единичной строки документации или текстового файла.
Дополнительные классы определены для поиска, парсинга, запуска и проверки тестов:
DocTestFinder
: Находит все строки документации в заданом модуле и используетDocTestParser
для созданияDocTest
из каждой строки документации, которая содержит интерактивные примеры.DocTestParser
: Создаёт объектDocTest
из строки (такой, как строка документации объекта).DocTestRunner
: выполняет тесты вDocTest
, и используетOutputChecker
для проверки их вывода.OutputChecker
: Сравнивает актуальный вывод из теста с ожидаемым выводом и определяет, совпадают ли они.
Отношения между этими классами выражено в следующей диаграмме:
list of:
+------+ +---------+
|module| --DocTestFinder-> | DocTest | --DocTestRunner-> results
+------+ | ^ +---------+ | ^ (printed)
| | | Example | | |
v | | ... | v |
DocTestParser | Example | OutputChecker
+---------+
Объекты DocTest¶
-
class
doctest.
DocTest
(examples, globs, name, filename, lineno, docstring)[source]¶ Набор тестов doctest, которые должны быть запущены в одном пространстве имён. Аргументы конструктора используются для инициализации атрибутов с теми же именами.
New in version 2.4.
DocTest
определяет следующие атрибуты. Они инициализируются конструктором и не должны изменяться напрямую.-
examples
¶ Список объектов
Example
, содержащих примеры интерактивных сеансов, которые должны быть проверены этим тестом.
-
globs
¶ Пространство имён (aka globals), в котором должны быть выполнены тесты. Это словарь, отображающий имена на значения. Все изменения, сделанные в этом пространстве имён тестами (например, добавление новых переменных) будут отражены в
globs
после завершения теста.
-
name
¶ Строковое имя, идентифицирующее
DocTest
. Обычно это имя объекта или файла, откуда был извлечён тест.
-
filename
¶ Имя файла, откуда был извлечён этот
DocTest
илиNone
, если оно не известно или еслиDocTest
не был получен из файла.
-
lineno
¶ Номер строки в
filename
, где начинаетсяDocTest
илиNone
, если номер строки не доступен. Отсчёт начинается с 0.
-
docstring
¶ Строка, из которой был извлечён тест или ‘None’, если строка не доступна, или тест не был получен из строки.
-
Объекты Example¶
-
class
doctest.
Example
(source, want[, exc_msg][, lineno][, indent][, options])[source]¶ Один интерактивный пример, состоящий из выражения Python и его ожидаемого вывода. Аргументы конструктора используются для инициализации атрибутов с теми же именами.
New in version 2.4.
Example
определяет следующие атрибуты. Они инициализируются конструктором и не должны изменяться напрямую.-
source
¶ Строка, содержащая исходный код примера. Этот исходный код состоит из одного выражения Python и всегда заканчивается новой строкой. Переход на новую строку конструктор добавляет автоматически.
-
want
¶ Ожидаемый вывод из исходного кода выполняемого примера (или в stdout, или трассировка в случае исключения).
want
завершается новой строкой, а если его вообще нет, то строка будет пустой. Конструктор добавляет переход на новую строку при необходимости.
-
exc_msg
¶ Сообщение об ошибке, генерируемое примером, если он должен вызывать исключение, или
None
, если оно не ожидается. Сообщение об ошибке сравнивается с возвращаемым значением функцииtraceback.format_exception_only()
. :attr:`exc_msg`завершается новой строкой, а если его вообще нет, то строка будет пустой. Конструктор добавляет переход на новую строку при необходимости.
-
lineno
¶ Номер строки в строке, содержащей пример, с которой начинается пример. Нумерация идёт с 0.
-
indent
¶ Отступ примера в содержащей его строке, то есть количество пробелов, которые находятся перед первым приглашением примера.
-
options
¶ Словарь, отображающий флаги опций на
True
илиFalse
, который используется для переопределения опций по умолчанию лдя этого примера. Все опции, не определённые в этом словаре будут иметь значения по умолчанию (как определно в атрибутеoptionflags
классаDocTestRunner
). По умолчанию, опции не заданы.
-
Объекты DocTestFinder¶
-
class
doctest.
DocTestFinder
([verbose][, parser][, recurse][, exclude_empty])[source]¶ Класс-оброаботчик, который используется для извлечения
DocTest
данного объекта из его строк документации и сторк документации вложенных объектов.DocTest
может быть извлечён из следующих типов объектов: модули, функции, классы, методы, статические методы, методы класса и свойства.Не обяхательный аргумент verbose может быть использован для отображения объектов, которые исследуются. По умолчанию -
False
(без вывода).Не обязательный аргумент parser определяет объект
DocTestParser
(или его замену), которая используется для извлечения тестов из строк документации.Если не обязательный арумент recurse ложный, тогда
DocTestFinder.find()
будет проверять лишь переданный объект, но не вложенные объекты.Если не обязательный аргумент exclude_empty ложный, тогда
DocTestFinder.find()
будет включать тесты для объектов с пустыми строками документации.New in version 2.4.
DocTestFinder
определяет следующие методы:-
find
(obj[, name][, module][, globs][, extraglobs])[source]¶ Возвращает список из
DocTest
, определённые в строках документации *obj*`a или его вложенных объектов.Не обязательный аргумент name определяет имя объекта; это имя будет использовано для создания имён для возвращаемых
DocTest
. Если name не определено, то будет использоватьсяobj.__name__
.Не обязательный параметр module является модулем, который содержит нужный объект. Если модуль не определён или равен None, тогда поисковик тестов попробует автоматически определить нужный модель. Модуль объекта используется...
- Как пространство имён по умолчанию, если globs не определён.
- Чтобы не дать DocTestFinder извлечь DocTests из объектов, которые импортируются из других модулей. (Объекты, чьи модули отличаются от module, будут проигнорированы.)
- Для поиска имени файла, содержащего объект.
- Для помощи в поиске номера строки объекта в файле.
Если module равен
False
, то не предпринимается попытки обнаружить модуль. Это не ясно и используется по большей части для тестирования самого doctest`a: если module равенFalse
илиNone
и при этом не может быть обнаружен автоматически, тогда все объекты, принадлежащие (не существующему) модулю, так что все содержащиеся объекты (рекурсивно) будут исследованны в поисках тестов.Глобальные переменные для каждого
DocTest
образовываются из соединения globs и extraglobs (определения из extraglobs переписывают определения из globs). Для каждогоDocTest
создаётся своя поверхностная копия. Если globs не определено, тогда его значения равны __dict__*`у модуля, если он определён, или ``{}`` в противном случае. Если *extraglobs не определён, вместо него используется{}
.
-
Объекты DocTestParser¶
-
class
doctest.
DocTestParser
[source]¶ Класс-обработчик, используемый для извлечения интерактивных примеров из строки и использует их для создания объектов
DocTest
.New in version 2.4.
DocTestParser
определяет следующие методы:-
get_doctest
(string, globs, name, filename, lineno)[source]¶ Извлекает все тесты из заданной строки и собирает их в объект
DocTest
.globs, name, filename и lineno являются атрибутами для нового объекта
DocTest
. Смотрите документацию дляDocTest
.
-
Объекты DocTestRunner¶
-
class
doctest.
DocTestRunner
([checker][, verbose][, optionflags])[source]¶ Класс-обработчик, используемый для извлечения и верификации интерактивных тестов в
DocTest
.Сравнение между ожидаемым выводом и реальным производится при помощи
OutputChecker
. Это сравнение можно настроить при помощи различных флагов, описанных подробно в разделе Опции/флаги. Если флагов не достаточно, то настроить сравнение можно передав подклассOutputChecker
в конструктор.То, как выполнитель теста отображает вывод, можно определить двумя способами. Первый - функция для вывода может быть передана в
TestRunner.run()
; эта функция будет вызвана для строки, которая должна быть отображена. По умолчанию этоsys.stdout.write
. Если этого не достаточно, то можно настроить вывод при помощи подкласса DocTestRunner, и переопределить его методыreport_start()
,report_success()
,report_unexpected_exception()
иreport_failure()
.Не обязательный именованый аргумент checker определяет объект
OutputChecker
(или его замену), который должен использоваться для сравнения итогового и ожидаемого вывода теста.Не обязательный именованый аргумент verbose определяет подробность сообщений
DocTestRunner
‘а. Если verbose равенTrue
, тогда будет выводиться информация о каждом примере в процессе его выполнения. Если verbose равенFalse
, тогда будут выведены только сообщения об ошибках. Если verbose не определено илиNone
, тогда подробный вывод будет выведен в том и только в том случае, если используется опция командной строки-v
.Не обязательный именованный аргумент optionflags может быть использован для управления того, как исполнитель тестов будет сравнивать реальный вывод и ожидаемым и как будут отображаться ошибки. Более подробно это описано в разделе Опции/флаги.
New in version 2.4.
DocTestParser
определяет следующие методы:-
report_start
(out, test, example)[source]¶ Сообщает, что исполнитель тестов готов выполнить данный пример. Этот метод предоставлен чтобы позволить подклассам
DocTestRunner
настроить свой вывод. Он не должен вызываться напрямую.example - пример, который должен быть обработан. test - тест, содержащий example. out - функция вывода информации, которая будет передана в
DocTestRunner.run()
.
-
report_success
(out, test, example, got)[source]¶ Сообщает, что данный пример был успешно выполнен. Этот метод предоставлен чтобы позволить подклассам
DocTestRunner
настроить свой вывод. Он не должен вызываться напрямую.example - пример, который должен быть обработан. got - реальный вывод примера. test - тест, содержащий example. out - функция вывода информации, которая будет передана в
DocTestRunner.run()
.
-
report_failure
(out, test, example, got)[source]¶ Сообщает, что данный пример был провален. Этот метод предоставлен чтобы позволить подклассам
DocTestRunner
настроить свой вывод. Он не должен вызываться напрямую.example - пример, который должен быть обработан. got - реальный вывод примера. test - тест, содержащий example. out - функция вывода информации, которая будет передана в
DocTestRunner.run()
.
-
report_unexpected_exception
(out, test, example, exc_info)[source]¶ Сообщает, что данный пример вызвал неожиданное исключение. Этот метод предоставлен чтобы позволить подклассам
DocTestRunner
настроить свой вывод. Он не должен вызываться напрямую.example - пример, который должен быть обработан. exc_info - кортеж, содержащий информацию о неожиданном исключении (как возвращаемый
sys.exc_info()
). test - тест, содержащий example. out - функция вывода информации, которая будет передана вDocTestRunner.run()
.
-
run
(test[, compileflags][, out][, clear_globs])[source]¶ Запускает пример в test (объект
DocTest
), и отображает результат при помощи “writer” из функции out.Примеры запускаются в пространстве имён
test.globs
. Если clear_globs истинен (по умолчанию), тогда пространство имён будет отчищено после выполнения теста, чтобы помочь со сбором мусора. Если Вы захотите проверить пространство имён после выполнения теста, используйте clear_globs=False.compileflags даёт набор флагов, которые должны использоваться компилятором Python при выполнении примера. Если не определён, то по умолчанию установлены флаги future-import, которые применяются к globs.
Вывод каждого примера проверяется при помощи проверщика вывода из класса
DocTestRunner
и результат форматируется методамиDocTestRunner.report_*()
.
-
summarize
([verbose])[source]¶ Выводит итог всех тестов, которые были запущены этим DocTestRunner и возвращает named tuple
TestResults(failed, attempted)
.Не обязательный аргумент verbose определяет то, насколько детальна будет итоговая информация. Если “подробность” не определена, тогда используется указатель “подробности” из
DocTestRunner
.Changed in version 2.6: Добавлен именованый кортеж.
-
Объекты OutputChecker¶
-
class
doctest.
OutputChecker
[source]¶ Класс, используемый для проверки совпадения реального вывода с ожидаемым. Он определяет два метода:
check_output()
, который сравнивает полученную пару выводов и возвращает true, если они совпадают; иoutput_difference()
, который возвращает строку, описывающую разницу между двумя выводами.New in version 2.4.
OutputChecker
определяет следующие методы:-
check_output
(want, got, optionflags)[source]¶ Возвращает
True
тогда и только тогда, когда реальный вывод теста (got) совпадает с желаемым (want). Эти строки всегда будут совпадающими, если они идентичны, но в зависимости от флагов исполнителя тестов некоторые не идентичные строки тоже могут рассматриваться как совпавшие. Более подробно это описано в разделе Опции/флаги.
-
Отладка¶
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) print 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) print x 3 (Pdb) step --Return-- > <doctest a[2]>(1)?()->None -> f(3) (Pdb) cont (0, 3) >>>
Changed in version 2.4: Была добавлена возможность использовать
pdb.set_trace()
внутри doctest`ов.
Фнукции, которые преобразуют doctest`ы в код Python и, возомжно, запускают полученный код в отладчике:
-
doctest.
script_from_examples
(s)[source]¶ Преобразует текст с примером в скрипт.
Аргумент s является строкой, содержащей пример doctest`а. Строка преобразуется в скрипт Python, где примеры в s преобразуются в обычный код, а всё остальное - в комментарии. Полученный скрипт возвращается как строка. Например:
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.
New in version 2.4.
-
doctest.
testsource
(module, name)[source]¶ Преобразует doctest из объекта в скрипт.
Аргумент module является объектом модуля или именем модуля с точками, содержащим объект, чьи строки документации нас интересуют. Аргумент name - имя (в модуле) объекта, чьи строки документации нам нужны. Результатом является строка, содержащая строки документации объекта, преобразованные в скрипт Python, как это описано выше в функции
script_from_examples()
. Например, если модульa.py
содержит функцию верхнего уровняf()
, тогда:import a, doctest print doctest.testsource(a, "a.f")
выведет скрипт для строки документации функции
f()
, где тест будет преобразован в код, а всё остальное помещено в комментакрии.New in version 2.3.
-
doctest.
debug
(module, name[, pm])[source]¶ Выполняет отладку doctest`а для объекта.
Аргументы module и name аналогичны для функции
testsource()
. Полученный скрипт для строк документации нужного объекта записываются во временный файл и затем этот файл запускается под котролем отладчика Python -pdb
.Поверхностная копия
module.__dict__
будет использоваться как для локального, так и для глобального контекста исполнения.Не обязательный аргумент pm определяет, будет ли использоваться “посмертная” отладка. Если pm истинен, тогда скрпит запускается напрямую и отладчик подключается только если скрипт прерывается возбуждением не обрабатываемого исключения. Если это происходит, включается “посмертная” отладка при помощи
pdb.post_mortem()
, получая объект трассировки из не перехваченного исключения. Если pm не определено или ложно, скрипт запускается с самого начала под отладчиком, передавая соответствующий вызов функцииexecfile()
вpdb.run()
.New in version 2.3.
Changed in version 2.4: Был добавлен аргумент pm.
-
doctest.
debug_src
(src[, pm][, globs])[source]¶ Выполняет отладку doctest`а из строки.
Эта функция похожа на
debug()
, за исключением того, что строка, содержащая примеры определена напрямую, через аргумент src.Не обязательный аргумент pm имеет то же значение, что и в функции
debug()
.Не обязательный аргумент globs передаёт словарь, который будет использоваться как локальный и глобальный контекст исполнения. Если он не определён или равен
None
, используется пустой словарь. Если определено, будет использоваться поверхностная копия словаря.New in version 2.4.
Класс DebugRunner
и специальные исключения, которые он может
вызывать, являются наиболее интересными для авторов тестирующих фреймворков,
и будут описаны тут лишь вкратце. Смотрите исходный код и в особенности
строки документации DebugRunner
‘а (которые являются тестами!),
где вы найдёте больше информации:
-
class
doctest.
DebugRunner
([checker][, verbose][, optionflags])[source]¶ Подкласс
DocTestRunner
‘а, который возбуждает исключение, как только обнаруживается ошибка. Если возникает неожиданное исключение, возбуждается исключениеUnexpectedException
, содержащее тест, пример и оригинальное исключение. Если вывод не совпадает, тогда возбуждается исключениеDocTestFailure
, содержащее тест, пример и реальный вывод.Информацию о параметрах конструктора и методах можно найти в документации к классу
DocTestRunner
в разделе Продвинутый API.
Есть два исключения, которые могут быть вызваны экземпляром
DebugRunner
:
-
exception
doctest.
DocTestFailure
(test, example, got)[source]¶ Исключение возбуждается классом
DocTestRunner
, чтобы сообщить, что реальный вывод примера не соответствует ожидаемому. Аргументы конструктора используются для инициализации атрибутов с тем же именем.
DocTestFailure
определяет следующие атрибуты:
-
DocTestFailure.
got
¶ Реальный вывод примера.
-
exception
doctest.
UnexpectedException
(test, example, exc_info)[source]¶ Исключение, вызываемое
DocTestRunner
, чтобы сообщить, что пример doctest`а вызвал неожиданное исключение. Аргументы конструктора используются для инициализации атрибутов с тем же именем.
UnexpectedException
определяет следующие атрибуты:
-
UnexpectedException.
exc_info
¶ Кортеж, содержащий информацию о неожиданном исключении в том виде, как он возвращается функцией
sys.exc_info()
.
Soapbox¶
Как упоминалось в предисловии, doctest
был создан для трёх основных
целей:
- Проверить примеры в строках документации.
- Регрессионного тестирования.
- Исполняемой документации / читабельных тестов.
Эти варианты использования имеют различные требования и очень важно их различать. В частности, заполнение ваших строк документации не ясными тестами испортит вашу документацию.
Когда вы пишете строки документации, тщательно выбирайте примеры для них.
Это отдельное искуство, которое может быть не очевидно на первый взгляд.
Примеры должны добавлять ценности документации. Хорошие примеры могут
быть ценее многих слов. Если все сделано внимательно, примеры будут иметь
неоценимое значение для пользователей и окупят время, необходимое, чтобы
собрать их; особенно с течением времени. Я до сих пор удивляюсь, как часто
один из моих примеров doctest
перестает работать после «безвредного»
изменения.
Doctest так же является замечательным инструментом для регрессионного тестирования, особенно если вы не экономите на пояснительном тексте. Смешивание текста и примеров позволяет гораздо проще отслеживать то, что конкретно тестируется и почему. Когда тест не удаётся, хороший текст позволяет гораздо проще понять, в чём именно проблема и как её исправить. Конечно, вы можете написать исчерпывающие комментарии в код теста, но мало кто так делает. Большинство обнаруживает, что использование docstest’a вместо этого приводит к более понятным тестам. Возможно это просто потому, что doctest позволяет писать текст проще, чем код, так как указывать комментарии в коде теста достаточно тяжело. Я думаю, что на самом деле это глубже: когда вы пишете тесты, вы хотите объяснить суть вашего скрипта и проиллюстрировать его примерами. Это само по себе ведёт к тому, что тестовые файлы начинаются с простых примеров и переходят к более сложным. В результате вы получаете последовательное изложение вместо набора изолированных функций, которые проверяют отдельные куски функционала, что выглядит гораздо менее понятно. Такой подход стирает разницу между тестированием и объяснением.
Регрессионное тестирование лучше всего ограничивается отдельными объектами и файлами. Есть несколько опций для организации тестов:
- Написание текстовых файлов, содержащих тесты и примеры, которые тестируют
файлы при помощи
testfile()
илиDocFileSuite()
. Это рекомендуется, хотя проще это сделать с новым проектом, который с самого начала расчитан на использование doctest. - Определить функции с названием
_regrtest_topic
, которые содержат одиночные строки документации, содержащие тесты для нужного топика. Эти функции могут находиться в том же файле, что и модуль, или быть вынесены в отдельный тестовый файл. - Определить словарь
__test__
, отображающий темы регрессионного тестирования на строки документации, содержащие тесты.
Footnotes
[1] | Примеры, содержащие как ожидаемый вывод, так и исключения не поддерживаются. Попытка угадать, где заканчивается одно и начинается другое способствует ошибкам и это делает тесты менее понятными. |