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