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
и в стандартный вывод будет выведен подробный отчёт обо всех проверенных примерах, а в конце — различные сводки.
Можно принудительно включить подробный режим, передав 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 — нет (трассировки исключений перехватываются другим способом).
-
Если в интерактивном сеансе вы продолжаете строку с помощью обратной косой черты или по какой-либо другой причине используете обратную косую черту, следует использовать необработанную строку документации, которая сохранит обратные косые черты в точности так, как вы их ввели:
>>> 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.
Добавлено в версии 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 -
Если этот параметр задан, выполнение завершается после первого неудачного примера, а остальные примеры не запускаются. Таким образом, будет зарегистрировано не более одного сбоя. Этот флаг может быть полезен при отладке, поскольку после первого сбоя примеры не будут запускаться и выводить отладочную информацию.
В командной строке 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()
{"Hermione", "Harry"}
может завершиться ошибкой! Один из способов обойти это — сделать так:
>>> foo() == {"Hermione", "Harry"}
True
Другой способ — сделать так:
>>> d = sorted(foo()) >>> d ['Harry', 'Hermione']
Есть и другие способы, но суть вы поняли.
Ещё одна неудачная идея — выводить объекты, в строковом представлении которых содержится адрес, например:
>>> 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 платформы, а качество таких библиотек сильно различается.
>>> 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(), продолжает выводить результаты для объектов без тестов. Для аргумента 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 при значении
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, 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 statement вместе с ожидаемым результатом. -
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.Вывод средства запуска тестов можно контролировать двумя способами. Во-первых, функции вывода можно передать в
TestRunner.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 при выполнении примеров. Если параметр не задан, по умолчанию используется набор флагов future-import, применимых к 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. -
Вы можете добавить вызов
pdb.set_trace()в пример doctest, и при выполнении этой строки вы попадёте в отладчик Python. Затем можно проверить текущие значения переменных и так далее. Например, предположим, чтоa.pyсодержит только эту строку документации модуля:""" >>> def f(x): ... g(x*2) >>> def g(x): ... print(x+3) ... import pdb; pdb.set_trace() >>> f(3) 9 """
Тогда интерактивный сеанс Python может выглядеть так:
>>> import a, doctest >>> doctest.testmod(a) --Return-- > <doctest a[1]>(3)g()->None -> import pdb; pdb.set_trace() (Pdb) list 1 def g(x): 2 print(x+3) 3 -> import pdb; pdb.set_trace() [EOF] (Pdb) p x 6 (Pdb) step --Return-- > <doctest a[0]>(2)f()->None -> g(x*2) (Pdb) list 1 def f(x): 2 -> g(x*2) [EOF] (Pdb) p x 3 (Pdb) step --Return-- > <doctest a[2]>(1)?()->None -> f(3) (Pdb) cont (0, 3) >>>
Функции, преобразующие doctest в код Python и, возможно, запускающие созданный код под отладчиком:
-
doctest.script_from_examples(s) -
Преобразует текст с примерами в скрипт.
Аргумент s — это строка, содержащая примеры doctest. Строка преобразуется в скрипт Python: примеры doctest в s преобразуются в обычный код, а всё остальное — в комментарии Python. Созданный скрипт возвращается в виде строки. Например,
import doctest print(doctest.script_from_examples(r""" Set x and y to 1 and 2. >>> x, y = 1, 2 Print their sum: >>> print(x+y) 3 """))выводит:
# Set x and y to 1 and 2. x, y = 1, 2 # # Print their sum: print(x+y) # Expected: ## 3
Эта функция используется внутри других функций (см. ниже), но может быть полезна и в тех случаях, когда нужно преобразовать интерактивный сеанс Python в скрипт Python.
-
doctest.testsource(module, name) -
Преобразует doctest объекта в скрипт.
Аргумент module — это объект модуля или имя модуля с точками, содержащего объект, doctest которого вас интересует. Аргумент name — имя объекта (внутри модуля), doctest которого вас интересует. Результат представляет собой строку, содержащую строку документации объекта, преобразованную в скрипт Python, как описано выше для
script_from_examples(). Например, если модульa.pyсодержит функцию верхнего уровняf(), тоimport a, doctest print(doctest.testsource(a, "a.f"))
выводит скриптовую версию строки документации функции
f(): примеры doctest преобразуются в код, а остальная часть помещается в комментарии.
-
doctest.debug(module, name, pm=False) -
Отлаживает doctest объекта.
Аргументы module и name имеют то же значение, что и для описанной выше функции
testsource(). Созданный скрипт Python для строки документации указанного объекта записывается во временный файл, а затем этот файл запускается под управлением отладчика Python,pdb.Для локального и глобального контекста выполнения используется поверхностная копия
module.__dict__.Необязательный аргумент pm определяет, будет ли использоваться посмертная отладка. Если значение pm истинно, скрипт запускается напрямую, а отладчик подключается только в том случае, если выполнение скрипта завершается необработанным исключением. Если это происходит, вызывается посмертная отладка с помощью
pdb.post_mortem(), которому передаётся объект трассировки из необработанного исключения. Если 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 естественно стремиться объяснить тонкости программы и проиллюстрировать их примерами. Это, в свою очередь, естественным образом приводит к тому, что тестовые файлы начинаются с простейших возможностей и последовательно переходят к более сложным случаям и граничным условиям. В результате получается связное повествование, а не набор разрозненных функций, которые, на первый взгляд, бессистемно проверяют отдельные части функциональности. Такой подход отличается от привычного и даёт другие результаты, стирая грань между тестированием и объяснением.
Регрессионные тесты лучше хранить в специально предназначенных для этого объектах или файлах. Есть несколько способов организовать тесты:
- Создайте текстовые файлы с тестовыми случаями в виде интерактивных примеров и проверяйте их с помощью
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.11/library/doctest.html