doctest — тестирование интерактивных примеров Python
Исходный код: Lib/doctest.py
Модуль doctest ищет фрагменты текста, похожие на интерактивные сеансы Python, а затем выполняет эти сеансы, чтобы убедиться, что они работают точно так, как показано. Существует несколько распространённых способов использования doctest:
- Проверять, что строки документации модуля актуальны, убеждаясь, что все интерактивные примеры по-прежнему работают так, как описано.
- Проводить регрессионное тестирование, проверяя, что интерактивные примеры из тестового файла или тестового объекта работают ожидаемым образом.
- Писать обучающую документацию для пакета, щедро иллюстрируя её примерами ввода и вывода. В зависимости от того, что выделено сильнее — примеры или пояснительный текст, — такой подход напоминает «литературное тестирование» или «исполняемую документацию».
Вот небольшой, но полноценный пример модуля:
"""
This is the "example" module.
The example module supplies one function, factorial(). For example,
>>> factorial(5)
120
"""
def factorial(n):
"""Return the factorial of n, an exact integer >= 0.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(30)
265252859812191058636308480000000
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
>>> factorial(30.1)
Traceback (most recent call last):
...
ValueError: n must be exact integer
>>> factorial(30.0)
265252859812191058636308480000000
It must also not be ridiculously large:
>>> factorial(1e100)
Traceback (most recent call last):
...
OverflowError: n too large
"""
import math
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
if __name__ == "__main__":
import doctest
doctest.testmod()
Если запустить example.py непосредственно из командной строки, doctest сделает своё дело:
$ python example.py $
Ничего не выведено! Это нормально и означает, что все примеры сработали. Передайте скрипту -v, и doctest выведет подробный журнал выполняемых проверок и итоговую сводку:
$ python example.py -v
Trying:
factorial(5)
Expecting:
120
ok
Trying:
[factorial(n) for n in range(6)]
Expecting:
[1, 1, 2, 6, 24, 120]
ok
И так далее, пока в итоге не появится:
Trying:
factorial(1e100)
Expecting:
Traceback (most recent call last):
...
OverflowError: n too large
ok
2 items passed all tests:
1 tests in __main__
8 tests in __main__.factorial
9 tests in 2 items.
9 passed and 0 failed.
Test passed.
$
Это всё, что нужно знать, чтобы начать эффективно использовать doctest! Попробуйте. В следующих разделах приведены все подробности. Обратите внимание, что в стандартном наборе тестов Python и его библиотеках есть множество примеров doctest. Особенно полезные примеры можно найти в стандартном тестовом файле Lib/test/test_doctest/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
В stdout будет выведен подробный отчёт обо всех проверенных примерах, а в конце — различные сводки.
Можно принудительно включить подробный режим, передав verbose=True в testmod(), или отключить его, передав verbose=False. В любом из этих случаев sys.argv не проверяется функцией testmod() (поэтому наличие или отсутствие -v ни на что не влияет).
Запустить testmod() можно также с помощью команды. Для этого укажите интерпретатору Python запустить модуль doctest непосредственно из стандартной библиотеки и передайте имена модулей в командной строке:
python -m doctest -v example.py
Это импортирует example.py как самостоятельный модуль и запустит для него testmod(). Обратите внимание, что это может работать неправильно, если файл входит в пакет и импортирует другие подмодули этого пакета.
Подробнее о testmod() см. в разделе Базовый API.
Простое использование: проверка примеров в текстовом файле
Ещё один простой способ применения doctest — тестирование интерактивных примеров в текстовом файле. Для этого можно использовать функцию testfile():
import doctest
doctest.testfile("example.txt")
Этот короткий скрипт выполняет и проверяет все интерактивные примеры Python, содержащиеся в файле example.txt. Содержимое файла рассматривается как одна большая строка документации; файл не обязан содержать программу на Python! Например, файл example.txt может содержать следующее:
The ``example`` module
======================
Using ``factorial``
-------------------
This is an example text file in reStructuredText format. First import
``factorial`` from the ``example`` module:
>>> from example import factorial
Now use it:
>>> factorial(6)
120
При запуске doctest.testfile("example.txt") обнаруживает ошибку в этой документации:
File "./example.txt", line 14, in example.txt
Failed example:
factorial(6)
Expected:
120
Got:
720
Как и testmod(), функция testfile() ничего не выводит, если только какой-либо пример не завершится ошибкой. Если пример завершится с ошибкой, в stdout будут выведены примеры, завершившиеся с ошибкой, и причины ошибок в том же формате, что и у testmod().
По умолчанию testfile() ищет файлы в каталоге вызывающего модуля. Описание необязательных аргументов, с помощью которых можно указать поиск файлов в других местах, см. в разделе Базовый API.
Как и для testmod(), подробность вывода testfile() можно настроить с помощью переключателя командной строки -v или необязательного именованного аргумента verbose.
Запустить testfile() можно также с помощью команды. Для этого укажите интерпретатору Python запустить модуль doctest непосредственно из стандартной библиотеки и передайте имена файлов в командной строке:
python -m doctest -v example.txt
Поскольку имя файла не заканчивается на .py, doctest определяет, что его нужно запустить с помощью testfile(), а не testmod().
Подробнее о testfile() см. в разделе Базовый API.
Как это работает
В этом разделе подробно рассматривается работа doctest: какие строки документации он проверяет, как находит интерактивные примеры, какой контекст выполнения использует, как обрабатывает исключения и как флаги параметров позволяют управлять его поведением. Это сведения, которые необходимо знать, чтобы писать примеры для doctest; информацию о запуске doctest для этих примеров см. в следующих разделах.
Какие строки документации проверяются?
Проверяются строка документации модуля, а также строки документации всех функций, классов и методов. Объекты, импортированные в модуль, не проверяются.
Кроме того, иногда нужно включить тесты в модуль, но не в текст справки, а значит, тесты не должны содержаться в строке документации. 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 не пытается в точности эмулировать какую-либо конкретную оболочку 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 — нет (трассировки исключений перехватываются другим способом).
-
Если в интерактивном сеансе вы продолжаете строку с помощью обратной косой черты или используете обратную косую черту по любой другой причине, следует использовать строку документации в формате raw, которая сохранит обратные косые черты точно в том виде, в каком вы их ввели:
>>> def f(x): ... r'''Backslashes in a raw docstring: m\n''' ... >>> print(f.__doc__) Backslashes in a raw docstring: m\n
В противном случае обратная косая черта будет интерпретирована как часть строки. Например, приведённый выше
\nбудет интерпретирован как символ новой строки. Также можно удвоить каждую обратную косую черту в версии doctest (и не использовать raw-строку):>>> 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
Обратите внимание, что трассировки обрабатываются особым образом. В частности, в переписанном примере использование ... не зависит от параметра ELLIPSIS в doctest. Многоточие в этом примере можно опустить или заменить тремя (или тремястами) запятыми или цифрами, либо отступом перед стенограммой скетча «Монти Пайтон».
Некоторые подробности достаточно прочитать один раз — запоминать их не нужно:
- 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.
Добавлено в версии 3.4: Параметр командной строки -o.
Первая группа параметров определяет семантику тестов и управляет тем, как doctest решает, совпадает ли фактический вывод с ожидаемым выводом примера:
-
doctest.DONT_ACCEPT_TRUE_FOR_1 -
По умолчанию, если блок ожидаемого вывода содержит только
1, фактический блок вывода, содержащий только1или толькоTrue, считается совпадением; аналогично для0иFalse. Если указан параметрDONT_ACCEPT_TRUE_FOR_1, ни одна из этих подстановок не выполняется. Поведение по умолчанию учитывает, что в Python тип результата многих функций изменился с целого числа на логическое значение; ожидающие «маленькое целое число» doctest-тесты в таких случаях по-прежнему работают. Вероятно, этот параметр когда-нибудь уберут, но не раньше чем через несколько лет.
-
doctest.DONT_ACCEPT_BLANKLINE -
По умолчанию, если блок ожидаемого вывода содержит строку, состоящую только из
<BLANKLINE>, эта строка соответствует пустой строке в фактическом выводе. Поскольку действительно пустая строка обозначает конец ожидаемого вывода, это единственный способ указать, что ожидается пустая строка. Если указан параметрDONT_ACCEPT_BLANKLINE, такая подстановка не выполняется.
-
doctest.NORMALIZE_WHITESPACE -
Если этот параметр указан, все последовательности пробельных символов (пробелов и символов новой строки) считаются равнозначными. Любая последовательность пробельных символов в ожидаемом выводе соответствует любой последовательности пробельных символов в фактическом выводе. По умолчанию пробельные символы должны совпадать точно. Параметр
NORMALIZE_WHITESPACEособенно полезен, когда строка ожидаемого вывода очень длинная и её нужно разбить на несколько строк в исходном тексте.
-
doctest.ELLIPSIS -
Если этот параметр указан, маркер многоточия (
...) в ожидаемом выводе может соответствовать любой подстроке фактического вывода. Это касается и подстрок, пересекающих границы строк, и пустых подстрок, поэтому лучше использовать этот параметр просто. Сложные варианты использования могут привести к таким же неожиданностям «ой, совпало слишком много!», какие часто возникают с.*в регулярных выражениях.
-
doctest.IGNORE_EXCEPTION_DETAIL -
Если этот параметр указан, 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 параметр
-fявляется сокращённой формой-o FAIL_FAST.Добавлено в версии 3.4.
-
doctest.REPORTING_FLAGS -
Битовая маска, объединяющая все перечисленные выше флаги вывода отчётов.
Также можно зарегистрировать новые имена флагов параметров, хотя это полезно только в том случае, если вы собираетесь расширять внутренние механизмы doctest с помощью наследования:
-
doctest.register_optionflag(name) -
Создаёт новый флаг параметра с заданным именем и возвращает его целочисленное значение.
register_optionflag()можно использовать при создании подклассовOutputCheckerилиDocTestRunner, чтобы создавать новые параметры, поддерживаемые вашими подклассами.register_optionflag()всегда следует вызывать с помощью следующего идиоматичного шаблона:MY_FLAG = register_optionflag('MY_FLAG')
Директивы
Директивы 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 в этом отношении сильно различается.
>>> 1./7 # risky 0.14285714285714285 >>> print(1./7) # safer 0.142857142857 >>> print(round(1./7, 6)) # much safer 0.142857
Числа вида I/2.**J надёжно работают на всех платформах, и я часто специально составляю примеры doctest так, чтобы они выдавали числа такого вида:
>>> 3./4 # utterly safe 0.75
Простые дроби также понятнее людям, а это делает документацию лучше.
Базовый 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 при значении
Falseаргумента module_relative нельзя.Необязательный аргумент globs задаёт словарь, который будет использоваться в качестве глобального пространства имён при выполнении примеров. Для doctest создаётся новая поверхностная копия этого словаря, поэтому выполнение примеров начинается с чистого состояния. По умолчанию или если
Noneиспользуется новый пустой словарь.Необязательный аргумент extraglobs задаёт словарь, объединяемый с глобальным пространством имён, используемым для выполнения примеров. Это работает так же, как
dict.update(): если в globs и extraglobs есть общий ключ, в объединённый словарь попадает соответствующее значение из extraglobs. По умолчанию или еслиNoneдополнительные глобальные переменные не используются. Это расширенная возможность, позволяющая параметризовать doctest. Например, можно написать doctest для базового класса, используя обобщённое имя класса, а затем повторно использовать его для проверки любого количества подклассов, передав словарь extraglobs, в котором обобщённое имя сопоставлено с проверяемым подклассом.Необязательный аргумент verbose при значении true выводит много сведений, а при значении false выводит только сведения о сбоях; по умолчанию или если
Noneон имеет значение true тогда и только тогда, когда'-v'находится вsys.argv.Необязательный аргумент report при значении true выводит сводку в конце, а при значении false ничего не выводит. В подробном режиме сводка содержит детальную информацию, в противном случае она очень краткая (фактически пустая, если все тесты пройдены).
Необязательный аргумент 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(), продолжать получать вывод для объектов без тестов. Для конструктора нового классаDocTestFinderаргумент exclude_empty по умолчанию имеет значение 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 при значении
Falseаргумента module_relative нельзя.Необязательный аргумент setUp задаёт функцию настройки набора тестов. Она вызывается перед запуском тестов в каждом файле. Функции setUp передаётся объект
DocTest. Функция setUp может получить доступ к глобальным переменным теста через атрибут globs переданного теста.Необязательный аргумент tearDown задаёт функцию завершения работы набора тестов. Она вызывается после запуска тестов в каждом файле. Функции tearDown передаётся объект
DocTest. Функция setUp может получить доступ к глобальным переменным теста через атрибут globs переданного теста.Необязательный аргумент globs — это словарь, содержащий начальные глобальные переменные для тестов. Для каждого теста создаётся новая копия этого словаря. По умолчанию globs — это новый пустой словарь.
Необязательный аргумент optionflags задаёт параметры doctest по умолчанию для тестов, формируемые операцией «ИЛИ» над отдельными флагами параметров. См. раздел Флаги параметров. Более удобный способ задать параметры отчётов описан ниже в функции
set_unittest_reportflags().Необязательный аргумент parser задаёт
DocTestParser(или его подкласс), который следует использовать для извлечения тестов из файлов. По умолчанию используется обычный синтаксический анализатор (то естьDocTestParser()).Необязательный аргумент encoding задаёт кодировку, которая должна использоваться для преобразования файла в Unicode.
Глобальная переменная
__file__добавляется к глобальным переменным, предоставляемым doctest, загруженным из текстового файла с помощьюDocFileSuite(). - Если module_relative имеет значение
-
doctest.DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, setUp=None, tearDown=None, optionflags=0, checker=None) -
Преобразует тесты doctest для модуля в
unittest.TestSuite.Возвращаемый объект
unittest.TestSuiteпредназначен для запуска средством unittest и выполняет каждый doctest в модуле. Если какой-либо doctest не пройдёт проверку, синтезированный модульный тест завершится с ошибкой, и будет возбуждено исключениеfailureException, указывающее имя файла с тестом и номер строки (иногда приблизительный).Необязательный аргумент module задаёт проверяемый модуль. Это может быть объект модуля или имя модуля (возможно, составное). Если аргумент не указан, используется модуль, вызвавший эту функцию.
Необязательный аргумент globs — это словарь, содержащий начальные глобальные переменные для тестов. Для каждого теста создаётся новая копия этого словаря. По умолчанию globs — это новый пустой словарь.
Необязательный аргумент extraglobs задаёт дополнительный набор глобальных переменных, который объединяется с globs. По умолчанию дополнительные глобальные переменные не используются.
Необязательный аргумент test_finder — это объект
DocTestFinder(или его полная замена), используемый для извлечения doctest из модуля.Необязательные аргументы setUp, tearDown и optionflags совпадают с аргументами описанной выше функции
DocFileSuite().Эта функция использует ту же технику поиска, что и
testmod().Изменено в версии 3.5:
DocTestSuite()возвращает пустойunittest.TestSuite, если модуль module не содержит строк документации, вместо того чтобы возбуждать исключениеValueError.
-
exception doctest.failureException -
Если тесты doctest, преобразованные в модульные тесты с помощью
DocFileSuite()илиDocTestSuite(), завершаются с ошибкой, возбуждается это исключение, указывающее имя файла с тестом и номер строки (иногда приблизительный).
Внутри DocTestSuite() создаёт unittest.TestSuite из экземпляров doctest.DocTestCase, а DocTestCase является подклассом unittest.TestCase. Класс DocTestCase здесь не документируется (это внутренняя деталь), но изучение его кода поможет разобраться в точных деталях интеграции с unittest.
Аналогично, DocFileSuite() создаёт unittest.TestSuite из экземпляров doctest.DocFileCase, а DocFileCase является подклассом DocTestCase.
Таким образом, оба способа создания unittest.TestSuite запускают экземпляры DocTestCase. Это важно по одной тонкой причине: при самостоятельном запуске функций doctest можно напрямую управлять используемыми параметрами doctest, передавая флаги параметров функциям doctest. Однако если вы пишете среду unittest, то именно unittest в конечном итоге управляет тем, когда и как запускаются тесты. Автор среды обычно хочет управлять параметрами отчётов doctest (например, задаваемыми параметрами командной строки), но передать параметры из unittest средствам запуска тестов doctest невозможно.
По этой причине doctest также поддерживает понятие флагов отчётности doctest, предназначенных для поддержки unittest, с помощью этой функции:
-
doctest.set_unittest_reportflags(flags) -
Задаёт используемые флаги отчётности
doctest.Аргумент flags принимает результат операции побитового ИЛИ над флагами параметров. См. раздел Флаги параметров. Можно использовать только «флаги отчётности».
Это глобальная настройка модуля, влияющая на все последующие doctest, запускаемые модулем
unittest: методrunTest()классаDocTestCaseпроверяет флаги параметров, заданные для тестового случая при создании экземпляраDocTestCase. Если флаги отчётности не заданы (что является обычным и ожидаемым случаем), флаги отчётностиdoctestизunittestобъединяются с флагами параметров операцией побитового ИЛИ, после чего полученные флаги передаются экземпляруDocTestRunner, созданному для запуска doctest. Если при создании экземпляраDocTestCaseбыли заданы какие-либо флаги отчётности, флаги отчётностиdoctestизunittestигнорируются.Функция возвращает значение флагов отчётности
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 -
Ожидаемый вывод при выполнении исходного кода примера (из stdout или трассировка стека в случае исключения).
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 — имя, идентифицирующее эту строку; оно используется только в сообщениях об ошибках.
-
Объекты 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 можно использовать для управления сравнением ожидаемого и фактического вывода средством запуска тестов, а также отображением сбоев. Дополнительную информацию см. в разделе Флаги параметров.
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.Примеры выполняются в пространстве имён
test.globs. Если clear_globs имеет значение true (по умолчанию), это пространство имён будет очищено после выполнения теста, чтобы упростить сборку мусора. Если после завершения теста необходимо проверить пространство имён, используйте clear_globs=False.compileflags задаёт набор флагов, которые компилятор Python должен использовать при выполнении примеров. Если значение не указано, по умолчанию используется набор флагов будущих импортов, применимых к globs.
Вывод каждого примера проверяется с помощью средства проверки вывода
DocTestRunner, а результаты форматируются методамиDocTestRunner.report_*().
-
summarize(verbose=None) -
Выводит сводку по всем тестовым случаям, выполненным этим DocTestRunner, и возвращает именованный кортеж
TestResults(failed, attempted).Необязательный аргумент verbose управляет подробностью сводки. Если подробность не указана, используется значение, заданное для
DocTestRunner.
-
Объекты 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 не указан или имеет ложное значение, скрипт с самого начала запускается под отладчиком посредством передачи соответствующего вызоваexec()вpdb.run().
-
doctest.debug_src(src, pm=False, globs=None) -
Отладить doctest в строке.
Эта функция похожа на описанную выше функцию
debug(), но строка с примерами doctest задаётся напрямую через аргумент src.Необязательный аргумент pm имеет то же значение, что и в описанной выше функции
debug().Необязательный аргумент globs задаёт словарь, который будет использоваться как локальный и глобальный контекст выполнения. Если он не указан или равен
None, используется пустой словарь. Если он указан, используется поверхностная копия словаря.
Класс DebugRunner и специальные исключения, которые он может вызывать, представляют наибольший интерес для авторов фреймворков тестирования, поэтому здесь мы лишь кратко их рассмотрим. Подробности см. в исходном коде и особенно в строке документации DebugRunner (которая сама является doctest!):
-
class doctest.DebugRunner(checker=None, verbose=None, optionflags=0) -
Подкласс
DocTestRunner, который вызывает исключение сразу при обнаружении ошибки. Если возникает неожиданное исключение, вызывается исключениеUnexpectedException, содержащее тест, пример и исходное исключение. Если вывод не совпадает, вызывается исключениеDocTestFailure, содержащее тест, пример и фактический вывод.Сведения о параметрах конструктора и методах см. в документации к
DocTestRunnerв разделе Расширенный API.
Экземпляры DebugRunner могут вызывать два исключения:
-
exception doctest.DocTestFailure(test, example, got) -
Исключение, вызываемое
DocTestRunner, чтобы сообщить, что фактический вывод примера doctest не совпал с ожидаемым. Аргументы конструктора используются для инициализации одноимённых атрибутов.
DocTestFailure определяет следующие атрибуты:
-
DocTestFailure.test -
Объект
DocTest, выполнявшийся в момент сбоя примера.
-
DocTestFailure.example -
Пример
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("{} failures out of {} tests".format(fail, total))
Сноски
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/doctest.html