doctest — Тестирование интерактивных примеров на Python

Модуль doctest ищет куски текста, которые выглядят как интерактивные сессии Python и затем выполняет эти сессии, чтобы проверить, что они работают точно так же, как показано. Есть несколько стандартных причин, чтобы использовать doctest:

  • Для того, чтобы проверить актуальность строк документации, убедившись, что все интерактивные примеры работают именно так, как задокументировано.
  • Чтобы организовать регрессионное тестирование, проверяя, что интерактивные примеры из тестового файла или тестового объекта работают как ожидается.
  • Чтобы написать руководство для пакета, иллюстрированное примерами ввода-вывода. В зависимости от того, на что обращается внимание - на примеры или на пояснительный текст, это можно назвать либо “литературным тестированием”, либо “исполняемой документацией”.

Вот полный, но небольшой пример модуля:

"""
Это модуль-пример.

Этот модуль предоставляет одну функцию - factorial().  Например,

>>> factorial(5)
120
"""

def factorial(n):
    """Возвращает факториал числа n, которое является числом >= 0.

    Если резульатат умещается в int, возвращается int.
    Иначе возвращается long.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> [factorial(long(n)) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(30)
    265252859812191058636308480000000L
    >>> factorial(30L)
    265252859812191058636308480000000L
    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0

    Можно вычислять факториал числа с десятичной частью, если она
    равна 0:
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
    >>> factorial(30.0)
    265252859812191058636308480000000L

    Кроме того, число не должно быть слишком большим:
    >>> factorial(1e100)
    Traceback (most recent call last):
        ...
    OverflowError: n too large
    """

    import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # перехватываем значения типа 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result


if __name__ == "__main__":
    import doctest
    doctest.testmod()

Если Вы запустите example.py прямо из командной строки, то, doctest выполнит своё волшебство:

$ python example.py
$

Тут нет никакого вывода! Это нормально и это означает, что все примеры работают. Передайте -v скрипту и doctest выведет детальный лог того, что он делает и подведёт итог в конце:

$ python example.py -v
Trying:
    factorial(5)
Expecting:
    120
ok
Trying:
    [factorial(n) for n in range(6)]
Expecting:
    [1, 1, 2, 6, 24, 120]
ok
Trying:
    [factorial(long(n)) for n in range(6)]
Expecting:
    [1, 1, 2, 6, 24, 120]
ok

И так далее, вплоть до:

Trying:
    factorial(1e100)
Expecting:
    Traceback (most recent call last):
        ...
    OverflowError: n too large
ok
2 items passed all tests:
   1 tests in __main__
   8 tests in __main__.factorial
9 tests in 2 items.
9 passed and 0 failed.
Test passed.
$

И это всё, что Вам надо знать начать использовать doctest! Просто начните. Остальные разделы дадут Вам больше информации. Обратите внимание, что есть много примеров доктестов в стандартных тестах Python и его библиотеках. Особенно полезные примеры можно найти в стандартном тестовом файле Lib/test/test_doctest.py.

Простое использование: Проверка примеров в Docstrings

Самый простой способ начать использовать doctest`ы (но не обязательно тот способ, которым Вы будете пользоваться и дальше) - завершать каждый модуль M с:

if __name__ == "__main__":
    import doctest
    doctest.testmod()

и тогда doctest будет проверять строки документации в модуле M.

Запуск модуля как скрипта приводит к тому, что примеры из строк документации выполняются и проверяются:

python M.py

Такое выполнение не отобразит ничего, пока какой-либо пример не пройдёт тест, и тогда сообщение об ошибке будет выведено на stdout, а последней строкой будет ***Test Failed*** N failures., где N - количетво не пройденных тестов.

Если запустить его с аргументом -v:

python M.py -v

то тогда на стандартный вывод попадёт детальный отчёт о всех тестах вместе с итогом в конце.

Вы можете принудительно включить подробный вывод, передав verbose=True в testmod(), или отключить его, передав verbose=False. В любом из этих двух случаев, sys.argv не проверяется функцией testmod() (так что наличие или отсутствие -v ничего не меняет).

Начиная с Python 2.6 появилось сокращение для коммандной строки для запуска testmod(). Вы можете указать интерпретатору Python запустить модуль doctest напрямую из стандартной библиотеки и передать этому модулю имя/имена как аргументы:

python -m doctest -v example.py

Такая команда импортирует example.py как отдельный модуль и запустит для него testmod(). Учтите, что это может работать не корректно, если файл является частью пакета и импортирует другие модули из этого пакета.

Более подробную информацию о testmod(), смотрите в разделе Базовый API.

Простое использование: Проверка примеров в текстовом файле

Другое простое приложение doctest`а - проверка интерактивных примеров из текстового файла. Это можно сделать при помощи функции testfile():

import doctest
doctest.testfile("example.txt")

Этот короткий скрипт выполняется и проверяет все интерактивные примеры Python, содержащиеся в файле example.txt. Содержимое файла должно соответствовать одной большой строке документации; файл не должен содержать программы на Python! Например, файл example.txt может содержать следующее:

Модуль ``example``
======================

Использование ``factorial``
-------------------

Это пример текстового файла в формате reStructuredText.  Во-первых, импортируем
``factorial`` из модуля ``example``:

    >>> from example import factorial

Теперь используем его:

    >>> factorial(6)
    120

Запуск doctest.testfile("example.txt") в таком случае найдёт ошибку в документации:

File "./example.txt", line 14, in example.txt
Failed example:
    factorial(6)
Expected:
    120
Got:
    720

Как и функция testmod(), testfile() не будет ничего отображать, если тесты проходят успешно. Если тест не проходит, тогда он и причина провала будут выведены в стандартный вывод в том же формате, что и testmod().

По умолчанию, testfile() ищет файлы в каталоге вызывающего модуля. Смотрите раздел Базовый API, где описываются аргументы, которые можно использовать, чтобы указать модулю, где искать файлы.

Как и у testmod(), “говорливость” testfile() может быть задана при помощи аргумента командной строки -v или аргумента verbose.

Начиная с Python 2.6 появилось сокращение для коммандной строки для запуска testfile(). Вы можете указать интерпретатору Python запустить модуль doctest напрямую из стандартной библиотеки и передать этому модулю имя/имена файла/файлов как аргументы:

python -m doctest -v example.txt

Так как имя файла не заканчивается на .py, doctest понимает, что должна быть запущена testfile(), а не testmod().

Более подробно testfile() описана в разделе Базовый API.

Как это работает

Этот раздел детально объясняет, как работает doctest: на какие строки документации он смотрит, как он обнаруживает интерактивные примеры, какой контекст исполнения он использует, как обрабатывает исключения и какие опции можно использовать для управления его поведением. Это та информацию, которая Вам понадобится для написания доктестов; информацию о запуске доктестов на этих примерах смотрите выше.

Какие строки документации проверяются?

Ищутся строки документации модуля, всех функций, классов и методов. Объекты, импортируемые в модуль не исследуются.

Кроме того, если существует M.__test__ и он “истинен”, то он должен быть словарём, и каждый элемент отображает строковое имя на объект функции, класса или строку. Ищутся строки документации для этих функций и классов, а строки трактуются как строки документации. В выводе ключ K из M.__test__ будет отображаться так

<имя M>.__test__.K

У всех найденных классов будет произведён рекурсивный поиск строк документации во вложеных методах и классах.

Changed in version 2.4: Концепция “private name” устарела и больше не документируется.

Как распознаются примеры из строк документации?

В большинстве случаев простое копирование-вставка из интерактивной консоли будет работать хорошо, но doctest не пытается точно эмулировать оболочку Python`а.

>>> # комментарии игнорируются
>>> x = 12
>>> x
12
>>> if x == 13:
...     print "yes"
... else:
...     print "no"
...     print "NO"
...     print "NO!!!"
...
no
NO
NO!!!
>>>

Весь ожидаемый вывод должен следовать непосредственно за последней строкой с кодом (начинающейся с '>>> ' или '... '), а вслед за выводом (если он есть) должна идти строка с '>>> ' или пустая строка.

The fine print:

  • Ожидаемый вывод не может содержать строку из пробелов, так как такая строка означает конец ожидаемого вывода. Если вывод должен содержать пустую строку, поместите <BLANKLINE> в пример строки документации там, где должна быть эта строка.

    New in version 2.4: Был добавлен <BLANKLINE>; в предыдущих версиях не было никакой возможности использовать ожидаемый вывод с пустыми строками.

  • Все символы табуляции заменяются пробелами, колонки содержат по 8 столбцов. Табуляция в выводе, полученном тестируемым кодом не изменяются. Поскольку все табуляции в примере вывода замещаются, значит, если вывод кода содержит знаки табуляции, единственный способ пройти тест - использовать опцию NORMALIZE_WHITESPACE или директиву. Или же, тест может быть переписан, чтобы он перехватывал вывод и сравнивал его с ожидаемым значением как часть теста. Такая обработка табуляции в исходном коде была получена методом проб и ошибок и зарекомендовала себя тоже глючным способом (This handling of tabs in the source was arrived at through trial and error, and has proven to be the least error prone way of handling them.) Возможно использовать другой алгоритм для обработки табуляции, если Вы напишете свой класс DocTestParser.

    Changed in version 2.4: Появилась замена табуляции на пробелы. В предыдущих версиях попытка сохранить табуляцию приводила к странным результатам.

  • Вывод в stdout захватывается, а вывод в stderr - нет (трассировка исключений захватывается по другому).

  • Если Вы продолжаете строку при помощи обратного слеша в интерактивной сессии или по какой-либо другой причине используете обратные слеши, Вы должны использовать “сырые” строки документации, которые сохранят ваши обратные слеши в первозданном виде:

    >>> def f(x):
    ...     r'''Backslashes in a raw docstring: m\n'''
    >>> print f.__doc__
    Backslashes in a raw docstring: m\n
    

    В противном случае, обратные слеши будут интерпретироваться как часть строки. Например, \n из примера выше, будет интерпретироваться как знак начала новой строки. Кроме того, Вы можете удвоить каждый бэкслеш (и не использовать “сырые” строки):

    >>> def f(x):
    ...     '''Backslashes in a raw docstring: m\\n'''
    >>> print f.__doc__
    Backslashes in a raw docstring: m\n
    
  • Стартовый отступ не имеет значения:

    >>> assert "Easy!"
          >>> import math
              >>> math.floor(1.9)
              1.0
    

    и от ожидаемого вывода будет отрезано столько пробелов, сколько предшествует '>>> ' в строке, начинающей пример.

Каков контекст выполнения?

По умолчанию, каждый раз, когда doctest находит стоки документации, которые надо протестировать, он использует shallow copy глобальных переменных модуля M‘, так что запуск теста не изменяет реальные значения переменных модуля, так что один тест модуля M не может оставить за собой “следы”, котоыре могут случайно привести к выполнению другого теста. Это означает, что примеры могут спокойно использовать любые имена, определённые на верхнем уровне модуля M, и имена, определённые ранее в этой строке документации. Примеры не могут видеть имена, определённые в другом примере (строке документации).

Вы можете принудительно использовать свой собственный словарь как контекст выполнения, передав его как аргумент globs в testmod() или testfile().

Что по поводу исключений?

Никаких проблем, при условии, что трассировка - единственный вывод примера: просто вставьте трассировку. [1] Так как трассировка содержит детали, которые постоянно изменяются (например, путь к файлу, номер строки), то это тот случай, когда doctest прикладывает все усилия, чтобы гибко обработать эту ситуацию.

Вот простой пример:

>>> [1, 2, 3].remove(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: list.remove(x): x not in list

Этот пример будет успешен, если возникнет исключение ValueError, с текстом list.remove(x): x not in list.

Ожидаемый вывод исключения должен начинаться с заголовка трассировки, который может быть одной из двух строк, находящимся на том же уровне отступа, что и первая строка примера:

Traceback (most recent call last):
Traceback (innermost last):

За заголовком трассировки может следовать стек трассировки, содержание которого игнорируется тестом. Обычно его опускают или копируют из интерактивной оболочки.

За стеком идёт наиболее интересная часть: строка(строки), содержащие тип исключения и информацию о нём. Обычно это самая последняя строка трассировки, но она может занимать и несколько строк, если информация об исключении содержит несколько строк:

>>> raise ValueError('multi\n    line\ndetail')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: multi
    line
detail

Последние три строчки (начиная с ValueError) сравниваются с типом исключения и информацией о нём, а всё остальное игнорируется.

Changed in version 2.4: Предыдущие версии не могли обрабатывать многострочную информацию об исключении.

Лучше всего считается опустить стек трассировки, если только он не добавляет важной информации к примеру. Так что последний пример лучше записать так:

>>> raise ValueError('multi\n    line\ndetail')
Traceback (most recent call last):
    ...
ValueError: multi
    line
detail

Обратите внимание, что трассировки трактуются очень по особенному. В частности, в переписанном выше примере использование ... не зависит от опции ELLIPSIS doctest’а. Вместо многоточия может быть любое количество других символов и даже транскрипт скетча Монти Пайтона.

Вот ещё несколько деталей, которые Вы должны прочитать но не обязаны помнить:

  • Doctest не может догадаться откуда берётся вывод - из трассировки исключения или от инструкции print. Таким образом, пример, который ожидает результат ValueError: 42 is prime будет пройден вне зависимости от того, на самом ли деле возникает исключение ValueError или просто выводится такой текст. На практике реальный вывод редко начинается с заголовка трассировки, так что это скорее всего не создат проблемы.

  • Каждая строка стека трассировки (если есть) должна иметь отступ больше, чем первая строка примера или начинаться с не алфавитно-цифрового символа. Первая строка за заголовком трассировки с тем же отступом и начинающаяся с алфавитно-цифрового символа считается началом информации об исключении. Что соответствует реальной трассировке.

  • Если определена опция IGNORE_EXCEPTION_DETAIL doctest`а, всё, что находится за самым левым двоеточием и вся информация о модуле в имени исключения игнорируется.

  • Интерактивная оболочка опускает заголовок трассировки для некоторых SyntaxError. Однако doctest использует заголовок трассировки для однозначного определения исполняемой части документации от не исполняемой. Так что в тех редких случаях, когда Вам надо протестировать SyntaxError, у которого опущен заголовок трассировки, Вам нужно в ручную добавить этот заголовок в ваш тестовый пример.

  • Для некоторых SyntaxError, Python отображает позицию символа на котором возникла ошибка при помощи маркера ^:

    >>> 1 1
      File "<stdin>", line 1
        1 1
          ^
    SyntaxError: invalid syntax
    

    Так как эта строка находится до обозначения типа исключения, она игнорируется. Например, следующий тест тоже будет считаться пройденным успешно, хотя маркер ^ находится в неверной позиции:

    >>> 1 1
      File "<stdin>", line 1
        1 1
        ^
    SyntaxError: invalid syntax
    

Опции/флаги

Есть некоторое количество опций, которые определяют поведение doctest’а. Символические имена для этих опций определены как константы модуля, которые можно совмещать при помощи or и передавать различным функциям. Этим имена так же можно использовать в директивах doctest`а.

Первая группа опций определяет семантику теста, контролирует то, как doctest определяет совпадает ли полученный вывод с ожидаемым или нет:

doctest.DONT_ACCEPT_TRUE_FOR_1

По умолчанию, если ожидаемый вывод содержит только 1, то результат, содержащий только 1 или просто True будет считаться верным, аналогично 0 и False. Когда используется DONT_ACCEPT_TRUE_FOR_1 такие замены не будут работать. Поведение по умолчанию необходимо потому, что Python изменяет возвращаемый тип многих функций с числового на логический и такое поведение doctest`a будет работать в этих ситуациях. Эта опция скорее всего будет убрана, но не в ближайшее время.

doctest.DONT_ACCEPT_BLANKLINE

По умолчанию, если блок вывода содержит только строку <BLANKLINE>, тогда эта строка соответствует пустой строке в реальном выводе. Так как реальная пустая строка ограничивает ожидаемый вывод, это единственный способ указать модулю, что пустая строка должна содержаться в результате теста. Когда используется DONT_ACCEPT_BLANKLINE, такая подстановка не разрешена.

doctest.NORMALIZE_WHITESPACE

Когда используется, все последовательности пробелов и знаков новой строки трактуются одинаково. Любая последовательность таких знаков в ожидаемом выводе будет соответствовать любой последовательности этих знаков в реальном выводе. По умолчанию пробелы должны совпасть точно. NORMALIZE_WHITESPACE особенно полезна, когда строка ожидаемого вывода очень длинная и Вы хотите разделить её на несколько строк в ожидаемом выводе.

doctest.ELLIPSIS

Когда используется, знак многоточия (...) в ожидаемом выводе будет соответствовать любой подстроке в реальном выводе. Это включает подстроки, которые пересекают границу строки и пустые строки, так что лучше использовать это для чего-то очевидного. Сложные примеры погут привести к ошибкам типа “ой, это совпало с большей частью строки, чем я думал”, как и .* в регулярных выражениях.

doctest.IGNORE_EXCEPTION_DETAIL

Когда используется, пример, который ожидает исключения будет считаться пройденным при возникновении исключения этого типа, даже если информация об исключении отличается. Например, пример, ожидающий ValueError: 42 будет считаться пройденным при возникновении ислкючения ValueError: 3*14, но не пройдёт при исключении TypeError.

В этом случае так же будет игнорироваться имя модуля, используемое в отчётах doctest`a в Python 3. Следовательно оба варианта при использовании этого флага будут работать в любой версии Python:

>>> raise CustomError('message')
Traceback (most recent call last):
CustomError: message

>>> raise CustomError('message')
Traceback (most recent call last):
my_module.CustomError: message

Обратите внимание, что ELLIPSIS можно использовать и для того, чтобы игнорировать сообщение исключения, но такой тест может всё равно не пройти в зависимости от того, печатается ли это сообщение как часть имени исключения или нет. Использование IGNORE_EXCEPTION_DETAIL в Python 2.3 - единственный ясный способ написать тест, который не обращает внимание на информацию об исключении и выполняется под Python 2.3 и раньше (эти релизы не поддреживают директивы doctest`a и игнорируют их как комментарии). Например:

>>> (1, 2)[3] = 'moo'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment

будет выполнен в Python 2.3 и более поздних версиях с этим флагом, хотя информация об исключении изменилась начиная с версии 2.4 и будет содержать “does not” вместо “doesn’t”.

Changed in version 2.7: IGNORE_EXCEPTION_DETAIL теперь игнорирует любую информацию исключения, относящуюся к модулю.

doctest.SKIP

Когда используется, вообще не запускает пример. Это может быть полезно в контексте когда примеры используются и как документация и как тесты, и пример должен быть включён как часть документации, но не должен проверяться. Например, вывод примера может быть случайным, или пример может зависеть от того, что не доступно во время теста.

Флаг SKIP так же можно использовать чтобы временно “закомментировать” пример.

New in version 2.5.

doctest.COMPARISON_FLAGS

Битовая маска, сочетающая все флаги сравнения, приведённые выше.

Вторая группа опций определяет то, как будет сообщено о том, что тест не пройден:

doctest.REPORT_UDIFF

Когда используется, ошибки, содержащие многострочный реальный и ожидаемый вывод отображаются при помощи unified diff.

doctest.REPORT_CDIFF

Когда используется, ошибки, содержащие многострочный реальный и ожидаемый вывод отображаются при помощи context diff.

doctest.REPORT_NDIFF

Когда используется, разница вычисляется при помощи difflib.Differ, используя тот же алгоритм, что и популярная утилита ndiff.py. Это единственный метод, который отмечает отличия как внутри строк, так и между строк. Например, если строка ожидаемого вывода содержит цифру 1, а реальный вывод содержит символ l, то строка появится с меткой, указывающий на несовпадающую позицию символа в строке.

doctest.REPORT_ONLY_FIRST_FAILURE

Если используется, показывает только первый неудачный тест и подавляет вывод всех остальных тестов. Это позволяет избежать сообщений об ошибках, которые возникают из-за ошибок в более ранних тестах. Но это же может скрыть не прошедшие тесты, которые были провалены вне зависимости от первой ошибки. Когда используется REPORT_ONLY_FIRST_FAILURE, остальные примеры выполняются и общее число ошибок тоже будет выведено, но сами ошибки будут скрыты.

doctest.REPORTING_FLAGS

Битовая маска, включающая все флаги отчётов выше.

Кроме того, можно зарегистрировать новый флаг, хотя это и бесполезно если только Вы не планируете расширять doctest через подклассы:

doctest.register_optionflag(name)[source]

Создаёт новый флаг с заданным именем и возвращает его числовое значение. register_optionflag() может быть использована при создании дочерних классов OutputChecker или DocTestRunner, чтобы добавить новые возможности, поддерживаемые вашим классом. register_optionflag() всегда должна вызываться следующим образом:

MY_FLAG = register_optionflag('MY_FLAG')

New in version 2.4.

Директивы

Директивы doctest`a можно использовать, чтобы изменить флаги для конкретных примеров. Директивами являются специальные комментарии, которые идут за исходным кодом примера:

directive             ::=  "#" "doctest:" directive_options
directive_options     ::=  directive_option ("," directive_option)\*
directive_option      ::=  on_or_off directive_option_name
on_or_off             ::=  "+" \| "-"
directive_option_name ::=  "DONT_ACCEPT_BLANKLINE" \| "NORMALIZE_WHITESPACE" \| ...

Не должно быть пробелов между + или - и именем опции директивы. Именем может быть имя любого флага, упомянутое выше.

Директива меняет поведение doctest’а только для одного примера. Используйте + чтобы включить нужное поведение или -, чтобы его отключить.

Например, этот текст успешно выполняется:

>>> print range(20) 
[0,   1,  2,  3,  4,  5,  6,  7,  8,  9,
10,  11, 12, 13, 14, 15, 16, 17, 18, 19]

Без директивы, он не прошёл бы потому что реальный вывод не содержит два пробела элементами списка, содержащими одну цифру,и потому что он располагается на одной строке. Следующий тест тоже будет успешным, и ему тоже нужна для этого директива:

>>> print range(20) 
[0, 1, ..., 18, 19]

Можно использовать несколько директив в одной строке, разделив их запятыми:

>>> print range(20) 
[0,    1, ...,   18,    19]

Если несколько директив используется для одного примера, то они комбинируются:

>>> print range(20) 
...                 
[0,    1, ...,   18,    19]

Как показывает предыдущий пример, Вы можете добавить строку ..., которая содержит только директивы, в ваш пример. Это может быть полезным в случае, если пример слишком длинный, чтобы директивы уместились на той же строке:

>>> print range(5) + range(10,20) + range(30,40) + range(50,60)
... 
[0, ..., 4, 10, ..., 19, 30, ..., 39, 50, ..., 59]

Обратите внимание, что так как по умолчанию все опции отключены и директивы применяются только к тому примеру, где они указаны, полезным бывает лишь включение опции (при помощи + в директиве). Однако флаги можно передать и функциям, которые запускают тесты, чтобы изменить их поведение. В этих случая может быть полезным отключение опции при помощи -.

New in version 2.4: Была добавлена поддержка директив doctest`a.

Предупреждения

doctest очень строго относится к тому, чтобы реальный вывод совпадал с ожидаемым. Если не совпадает даже один символ - тест всё равно будет провален. Возможно, это приведёт Вас к некоторым открытиям в том, что гарантируется, а что нет в выводе Python. Например когда Вы выводите словарь, Python не гарантирует, что пары ключ-значение будут напечатаны в каком-то определённом порядке, так что тест вроде:

>>> foo()
{"Hermione": "hippogryph", "Harry": "broomstick"}

опасен! Один из способов обойти это:

>>> foo() == {"Hermione": "hippogryph", "Harry": "broomstick"}
True

Другой:

>>> d = foo().items()
>>> d.sort()
>>> d
[('Harry', 'broomstick'), ('Hermione', 'hippogryph')]

Есть и другие, но Вы поняли.

Ещё одна плохая идея - сравнивать информацию об адресе объекта, как:

>>> id(1.0) # будет периодически неуспешным
7948648
>>> class C: pass
>>> C()   # по умолчанию repr() отобразит адрес объекта
<__main__.C instance at 0x00AC18F0>

Директива ELLIPSIS поможет Вам с последним примером:

>>> C() 
<__main__.C instance at 0x...>

Числа с плавающей точкиой тоже являются причиной небольших отличий на разных платформах, так как Python обращается к библиотеке С платформы для форматирования этих чисел, а эти библиотеки очень отличаются в этом вопросе:

>>> 1./7  # опасно
0.14285714285714285
>>> print 1./7 # безопаснее
0.142857142857
>>> print round(1./7, 6) # ещё лучше
0.142857

Числа в виде I/2.**J безопасны на всех платформах и я часто придумывают тесты чтобы получитьь число в такой форме:

>>> 3./4  # очень безопасно
0.75

Кроме того, людям проще воспринимать простые дроби и это поможет Вам лучше написать документацию.

Базовый API

Функции testmod() и testfile() предоставляют простой интерфейс к doctest, которого должно быть достаточно для большинства случаев. Для менее формального знакомства с этими функциями смотрите разделы Простое использование: Проверка примеров в Docstrings и Простое использование: Проверка примеров в текстовом файле.

doctest.testfile(filename[, module_relative][, name][, package][, globs][, verbose][, report][, optionflags][, extraglobs][, raise_on_error][, parser][, encoding])[source]

Все аргументы, кроме filename не являются обязательными и должны быть указаны как именованные аргументы.

Запускает тесты в файле filename. Возвращает (failure_count, test_count).

Не обязательный аргумент module_relative определяет, как должно быть интерперетировано имя файла:

  • Если module_relative True (по умолчанию), тогда filename определяет OС-независимый относительный путь. По умолчанию, путь берётся от каталога вызывающего модуля, но если определён аргумент package, тогда путь берётся относительно этого пакета. Чтобы обеспечить независимость от ОС filename должно использовать в строке пути символ /, и он может быть не абсолютным (то есть, не начинаться с /).
  • Если module_relative False, тогда filename определяет зависящий от ОС путь. Он может быть абсолютным или относительным; относительный путь разрешается относительно текущего рабочего каталога.

Не обязательный аргумент name указывает имя теста; по умаолчанию, или если равен None, используется os.path.basename(filename).

Не обязательный аргумент package является пакетом Python или именем пакета Python, чей каталог надо использовать как базовый каталог для зависящих от модуля имён файлов. Если пакет не определён, то используется каталог вызывающего модуля. Ошибкой является указывать package, если module_relative False.

Не обязательный аргумент globs передаёт словарь, который будет использоваться для глобальных переменных при выполнении теста. Новая поверхностная копия этого словаря создаётся для doctest, так что тесты начинваются с “чистого” состояния. По умолчанию, или если аргумент равен None, используется новый пустой словарь.

Не обязательный аргумент extraglobs содержит словарь, который сливается с глобальным словарём, используемым для выполнения тестов. Это работает как dict.update(): если globs и extraglobs имеют общие ключи, то в итоговом словаре будет значение из extraglobs. По умолчанию, или если значение аргумента равно None, никаких дополнительных значений не используется. Это ещё одна возможность настройки теста. Например, тест может быть написан для базового класса, используя имя класса по умолчанию, а затем его можно использовать для любого другого количества подклассов, просто передав словарь extraglobs, который отображает имя по умолчанию на тот подкласс, который надо проверить.

Не обязательный аргумент verbose приводит к выводу кучи информации, если он истинен, или выводятся только сообщения об ошибках, если ложен, не задан или равен None, он будет истинен только если '-v' присутствует в sys.argv.

Не обязательный аргумент report выводит итог всех тестов, если истинен, иначе по завершению тестов не будет напечатано ничего. В “словоохотливом” режиме итог будет детальным, иначе - очень коротким (или пустым, если все тесты пройдены).

Не обязательный аргумент optionflags складывает все остальные флаги. Смотрите раздел Опции/флаги.

Не обязательный аргумент raise_on_error по умолчанию ложный. Если он истинный, то вызывается исключение при первой ошибке в тесте или неожиданном исключении. Это позволяет посмертно отладить ошибки. Поведение по умолчанию - продолжать выполнение тестов.

Не обязательный аргумент parser определяет DocTestParser (или подкласс), который будет использован для извлечения тестов из файлов. По умолчанию - обычный парсер (то есть, DocTestParser()).

Не обязательный аргумент encoding определяет кодировку, которая должна быть использована для перобразования файла в юникод.

New in version 2.4.

Changed in version 2.5: Был добавлен парамент encoding.

doctest.testmod([m][, name][, globs][, verbose][, report][, optionflags][, extraglobs][, raise_on_error][, exclude_empty])[source]

Все аргументы не обязательные, и все, за исключением m, должны передаваться как именованные аргументы.

Проверяет примеры в строках документации в функциях и классах, достигнутых из модуля m (или модуля __main__, если m не указан или None), начиная с m.__doc__.

Кроме того, проверяет примеры, доступные из словаря m.__test__, если он существует и не равен None. m.__test__ отражает имена (строки) на функции, классы или строки; в функциях и классах ищутся тесты, строки рассматриваются напрямую как строки документации.

Рассматриваются только строки документации, принадлежащие объектам из модуля m.

Возвращает (failure_count, test_count).

Не обязательный аргумент name содержит имя модуля; по умолчанию, или если равен None, используется m.__name__.

Не обязательный аргумент exclude_empty по умолчанию ложный. Если истинен, то объект, для которого не найдены строки документации исключается из рассмотрения. Значение по умолчанию является “костылём” для совместимости, так что код, использующий doctest.master.summarize() в месте с testmod() продолжает создавать вывод для объектов без тестов. Для нового класса DocTestFinder аргумент exclude_empty по умолчанию истинен.

Не обязательные аргументы extraglobs, verbose, report, optionflags, raise_on_error и globs аналогичны этим же аргументам для функции testfile(), за исключением того, что globs по умолчанию равен m.__dict__.

Changed in version 2.3: Был добавлен парамент optionflags.

Changed in version 2.4: Были добавлены параметры extraglobs, raise_on_error и exclude_empty.

Changed in version 2.5: Не обязательный аргумент isprivate, не рекомендуемый с 2.4, был удалён.

Кроме того, есть ещё функция для запуска doctest`ов, ассоциированных с отедльным объектом. Эта функция присутствует для обратной совместимости. Запрещать её не планируется, но она используется крайне редко:

doctest.run_docstring_examples(f, globs[, verbose][, name][, compileflags][, optionflags])[source]

Запускает тесты, ассоциированные с объектом f; например, f может быть модулем, функцией или классом.

В качестве контекста выполнения используется поверхностная копия аргумента globs.

Не обязательный аргумент name используется в сообщениях об ошибках и по умолчанию равно "NoName".

Если не обязательный аргумент verbose истинен, вывод будет, даже если никаких ошибок не было. По умолчанию вывод будет только если в тестах возникают ошибки.

Не обязательный аргумент compileflags даёт набор флагов, которые должны использоваться компилятором Python для выполнения тестов. По умолчанию, или если равен None, флаги определяются в соответствии с набором будущих возможностей, обнаруженных в globs (flags are deduced corresponding to the set of future features found in globs).

Не обязательный аргумент optionflags работает так же, как в функции testfile().

Unittest API

По мере того, как ваша коллекция модулей с doctest`ами будет расти, Вы захотите получить возможность запускать все эти тесты систематически. До Python 2.4, doctest имел плохо документированный класс Tester, который предоставлял устаревший способ скомбинировать тесты из нескольких модулей. Tester был слаб и более серьёзные фреймворки для тестирования построине на модуле unittest, который даёт более гибкие способы скомбинировать тесты из нескольких источников. Так, в Python 2.4, Tester doctest‘а уже не рекомендуется использовать и doctest предоставляет две функции, которые можно использовать для создания набора тестов unittest из модулей и текстовых файлов, содержащий строки документации. Для итеграции с поисковщиком тестов unittest, добавьте функцию load_tests() в ваш тестовый модуль:

import unittest
import doctest
import my_module_with_doctests

def load_tests(loader, tests, ignore):
    tests.addTests(doctest.DocTestSuite(my_module_with_doctests))
    return tests

Есть две основные функции для создания экземлпяров unittest.TestSuite из текстовых файлов и модулей с doctest`ами:

DocFileSuite(*paths, [module_relative][, package][, setUp]

[, tearDown][, globs][, optionflags][, parser][, encoding])

Преобразует тест doctest из одного и более текстового файла в unittest.TestSuite.

Возвращаемый unittest.TestSuite должен быть запущен фреймворком unittest и выполняет интерактивные примеры в каждом файле. Если тест в каком-либо файле не проходит, тогда полученный unit test провален и возбуждается failureException, показывающее имя файла, содержащее тест и (иногда) номер строки.

Принимает один или более путей (в виде строк) к текстовым файлам, которые надо проверить.

Опции могут быть указаны как именованные аргументы:

Не обязательный аргумент module_relative определяет то, как имя файла в paths должно интерпретироваться:

  • Если module_relative True (по умолчанию), тогда filename определяет OС-независимый относительный путь. По умолчанию, путь берётся от каталога вызывающего модуля, но если определён аргумент package, тогда путь берётся относительно этого пакета. Чтобы обеспечить независимость от ОС filename должно использовать в строке пути символ /, и он может быть не абсолютным (то есть, не начинаться с /).
  • Если module_relative False, тогда filename определяет зависящий от ОС путь. Он может быть абсолютным или относительным; относительный путь разрешается относительно текущего рабочего каталога.

Не обязательный аргумент package является пакетом Python или именем пакета Python, чей каталог надо использовать как базовый каталог для зависящих от модуля имён файлов в paths. Если пакет не определён, то используется каталог вызывающего модуля. Ошибкой является указывать package, если module_relative False.

Не обязательный аргумент setUp определяет настроечную функцию для каждого набора тестов. Она вызывается перед запуском тестов в каждом файле. Функция setUp будет передана объекту DocTest. Эта функция имеет доступ к глобальным переменным теста через атрибут globs выполняемого теста.

Не обязательный аргумент tearDown определяет “очищающую” функцию для набора тестов. Она вызывается после запуска тестов в каждом файле. Функция tearDown будет передана объекту DocTest. Эта функция имеет доступ к глобальным переменным теста через атрибут globs выполняемого теста.

Не обязательный аргумент globs является словарём, содержащим начальные глобальные значения переменных для теста. Новая копия этого словаря создаётся для каждого теста. По умолчанию globs является пустым словарём.

Не обязательный аргумент optionflags определяет опции по умолчанию для теста, создаваемый при помощи “или” для отдельных опций. Смотрите раздел Опции/флаги. Смотрите ниже описание функции set_unittest_reportflags(), где описан лучший способ задания опций оповещения.

Не обязательный аргумент parser определяет DocTestParser (или подкласс), который должен быть использован для извлечения тестов из файлов. По умолчанию это обычный парсер (то есть, DocTestParser()).

Не обязательный аргумент encoding определяет кодировку, которая должна быть использована для преобразования файла в юникод.

New in version 2.4.

Changed in version 2.5: Был добавлен __file__ к глобальным переменным, предоставляемым doctest`ам, загружаемым из текстового файла при помощи DocFileSuite().

Changed in version 2.5: Был добавлен параметр encoding.

Note

В отличие от testmod() и DocTestFinder, эта функция возбуждает исключение ValueError, если module не содержит строк документации. Вы можете избежать этого исключения, передав экземпляр DocTestFinder как аргумент test_finder с именованным аргументом exclude_empty установленным в False:

>>> finder = doctest.DocTestFinder(exclude_empty=False)
>>> suite = doctest.DocTestSuite(test_finder=finder)
DocTestSuite([module][, globs][, extraglobs][, test_finder]

[, setUp][, tearDown][, checker])

Преобразует тесты для модуля в unittest.TestSuite.

Возаращаемый unittest.TestSuite должен быть запущен под фреймворком unittest и он выполнит каждый тест в модуле. Если какой-либо тест не проходит, тогда синтезированный тест тоже не пройдёт и будет вызвано ислкючение failureException, которое сообщит имя файла, содержащего тест и (иногда приблизительно) номер строки.

Не обязательный аргумент module предоставляет модуль, который нужно протестировать. Это может быть как объектом модуля, так и имя модуля (даже с точками). Если не определено, используется модуль, вызывашинй эту функцию.

Не обязательный аргумент globs является словарём, содержащим начальные глобальные значения переменных для теста. Новая копия этого словаря создаётся для каждого теста. По умолчанию globs является пустым словарём.

Не обязательный аргумент extraglobs определяет дополнительные глобальные переменные, которые сливаются с globs. По умолчанию никаких дополнитеьльных переменных не используется.

Не обязательный аргумент test_finder является объектом DocTestFinder (или drop-in replacement), который будет использован для извлечения теста из модуля.

Не обязательные аргументы setUp, tearDown, и optionflags аналогичны этим аргументам в функции DocFileSuite().

New in version 2.3.

Changed in version 2.4: Были добавлены параметры globs, extraglobs, test_finder, setUp, tearDown и optionflags; эта функция теперь использует тот же самый механизм поиска, что и testmod().

Под “капотом” DocTestSuite() создаёт unittest.TestSuite из экземпляров doctest.DocTestCase, а DocTestCase является дочерним классом unittest.TestCase. DocTestCase не задокументирован тут (это внутренее устройство модуля), но изучение его кода может ответить на некоторые вопросы по поводу подробностей интеграции модуля unittest.

Похожим образом DocFileSuite() создаёт unittest.TestSuite из экземпляров doctest.DocFileCase, а DocFileCase является подклассом DocTestCase.

Таким образом оба способа создания unittest.TestSuite запускают экземпляр DocTestCase. Это важно по одной причине: когда Вы запускаете функции модуля doctest, Вы можете контролировать опции doctest непосредственно, передав флаги функциям модуля doctest. Однако, если Вы пишете фреймворк unittest, unittest полностью контролирует как и когда тест запускается. Автор фрейморвка обычно хочет контролировать опции оповещения doctest (возможно, например, определяемые опциями командной строки), но тут нет никакого способа передать опции через unittest в исполнитель тестов doctest.

По этой причине doctest так же поддерживает флаги оповещения doctest предназначенные для поддержки unittest, при помощи этих функций:

doctest.set_unittest_reportflags(flags)[source]

Задаёт флаги doctest для оповещения.

Аргумент flags соеденияет при помощи “или” флаги опций. Смотрите раздел Опции/флаги. Можно использовать только флаги оповещения.

Эта настройка глобальна для всего модуля и влияет на все следующие тесты, которые будут запущены модулем unittest: метод runTest() класса DocTestCase смотрит на флаги опций, определённые для набора тестов, когда создаётся экземпляр DocTestCase. Если флаги оповещений не были определены (что является обычным случаем), флаги оповещения doctest‘а для unittest складываются при помощи “или” в флаги опций и они передаются в создаваемый экземпляр DocTestRunner, который будет запускать тесты. Если флаги были определены при создании класса DocTestCase, флаги оповещения doctest‘а для unittest игнорируются.

Значение флагов оповещения unittest, которое оно было до вызова функции, возвращается функцией.

New in version 2.4.

Продвинутый API

Базовый API является просто оболочкой, позволяющей проще использовать doctest. Он достаточно гибкий и должен удволетворять большую часть потребностей пользователей. Однако, если Вам нужен более детальный контроль над тестированием или Вы хотите расширить возможности doctest’а, тогда Вы должны использовать продвинутый API.

Продвинутый API работает вокруг двух классов-контейнеров, которые используются для хранения интерактивных примеров, извлечённых из строк документации:

  • Example: одиночное выражение Python`а, в паре с его ожидаемым результатом.
  • DocTest: набор Exampleов, обычно извлечённых из единичной строки документации или текстового файла.

Дополнительные классы определены для поиска, парсинга, запуска и проверки тестов:

  • DocTestFinder: Находит все строки документации в заданом модуле и использует DocTestParser для создания DocTest из каждой строки документации, которая содержит интерактивные примеры.
  • DocTestParser: Создаёт объект DocTest из строки (такой, как строка документации объекта).
  • DocTestRunner: выполняет тесты в DocTest, и использует OutputChecker для проверки их вывода.
  • OutputChecker: Сравнивает актуальный вывод из теста с ожидаемым выводом и определяет, совпадают ли они.

Отношения между этими классами выражено в следующей диаграмме:

                            list of:
+------+                   +---------+
|module| --DocTestFinder-> | DocTest | --DocTestRunner-> results
+------+    |        ^     +---------+     |       ^    (printed)
            |        |     | Example |     |       |
            v        |     |   ...   |     v       |
           DocTestParser   | Example |   OutputChecker
                           +---------+

Объекты DocTest

class doctest.DocTest(examples, globs, name, filename, lineno, docstring)[source]

Набор тестов doctest, которые должны быть запущены в одном пространстве имён. Аргументы конструктора используются для инициализации атрибутов с теми же именами.

New in version 2.4.

DocTest определяет следующие атрибуты. Они инициализируются конструктором и не должны изменяться напрямую.

examples

Список объектов Example, содержащих примеры интерактивных сеансов, которые должны быть проверены этим тестом.

globs

Пространство имён (aka globals), в котором должны быть выполнены тесты. Это словарь, отображающий имена на значения. Все изменения, сделанные в этом пространстве имён тестами (например, добавление новых переменных) будут отражены в globs после завершения теста.

name

Строковое имя, идентифицирующее DocTest. Обычно это имя объекта или файла, откуда был извлечён тест.

filename

Имя файла, откуда был извлечён этот DocTest или None, если оно не известно или если DocTest не был получен из файла.

lineno

Номер строки в filename, где начинается DocTest или None, если номер строки не доступен. Отсчёт начинается с 0.

docstring

Строка, из которой был извлечён тест или ‘None’, если строка не доступна, или тест не был получен из строки.

Объекты Example

class doctest.Example(source, want[, exc_msg][, lineno][, indent][, options])[source]

Один интерактивный пример, состоящий из выражения Python и его ожидаемого вывода. Аргументы конструктора используются для инициализации атрибутов с теми же именами.

New in version 2.4.

Example определяет следующие атрибуты. Они инициализируются конструктором и не должны изменяться напрямую.

source

Строка, содержащая исходный код примера. Этот исходный код состоит из одного выражения Python и всегда заканчивается новой строкой. Переход на новую строку конструктор добавляет автоматически.

want

Ожидаемый вывод из исходного кода выполняемого примера (или в stdout, или трассировка в случае исключения). want завершается новой строкой, а если его вообще нет, то строка будет пустой. Конструктор добавляет переход на новую строку при необходимости.

exc_msg

Сообщение об ошибке, генерируемое примером, если он должен вызывать исключение, или None, если оно не ожидается. Сообщение об ошибке сравнивается с возвращаемым значением функции traceback.format_exception_only(). :attr:`exc_msg`завершается новой строкой, а если его вообще нет, то строка будет пустой. Конструктор добавляет переход на новую строку при необходимости.

lineno

Номер строки в строке, содержащей пример, с которой начинается пример. Нумерация идёт с 0.

indent

Отступ примера в содержащей его строке, то есть количество пробелов, которые находятся перед первым приглашением примера.

options

Словарь, отображающий флаги опций на True или False, который используется для переопределения опций по умолчанию лдя этого примера. Все опции, не определённые в этом словаре будут иметь значения по умолчанию (как определно в атрибуте optionflags класса DocTestRunner). По умолчанию, опции не заданы.

Объекты DocTestFinder

class doctest.DocTestFinder([verbose][, parser][, recurse][, exclude_empty])[source]

Класс-оброаботчик, который используется для извлечения DocTest данного объекта из его строк документации и сторк документации вложенных объектов. DocTest может быть извлечён из следующих типов объектов: модули, функции, классы, методы, статические методы, методы класса и свойства.

Не обяхательный аргумент verbose может быть использован для отображения объектов, которые исследуются. По умолчанию - False (без вывода).

Не обязательный аргумент parser определяет объект DocTestParser (или его замену), которая используется для извлечения тестов из строк документации.

Если не обязательный арумент recurse ложный, тогда DocTestFinder.find() будет проверять лишь переданный объект, но не вложенные объекты.

Если не обязательный аргумент exclude_empty ложный, тогда DocTestFinder.find() будет включать тесты для объектов с пустыми строками документации.

New in version 2.4.

DocTestFinder определяет следующие методы:

find(obj[, name][, module][, globs][, extraglobs])[source]

Возвращает список из DocTest, определённые в строках документации *obj*`a или его вложенных объектов.

Не обязательный аргумент name определяет имя объекта; это имя будет использовано для создания имён для возвращаемых DocTest. Если name не определено, то будет использоваться obj.__name__.

Не обязательный параметр module является модулем, который содержит нужный объект. Если модуль не определён или равен None, тогда поисковик тестов попробует автоматически определить нужный модель. Модуль объекта используется...

  • Как пространство имён по умолчанию, если globs не определён.
  • Чтобы не дать DocTestFinder извлечь DocTests из объектов, которые импортируются из других модулей. (Объекты, чьи модули отличаются от module, будут проигнорированы.)
  • Для поиска имени файла, содержащего объект.
  • Для помощи в поиске номера строки объекта в файле.

Если module равен False, то не предпринимается попытки обнаружить модуль. Это не ясно и используется по большей части для тестирования самого doctest`a: если module равен False или None и при этом не может быть обнаружен автоматически, тогда все объекты, принадлежащие (не существующему) модулю, так что все содержащиеся объекты (рекурсивно) будут исследованны в поисках тестов.

Глобальные переменные для каждого DocTest образовываются из соединения globs и extraglobs (определения из extraglobs переписывают определения из globs). Для каждого DocTest создаётся своя поверхностная копия. Если globs не определено, тогда его значения равны __dict__*`у модуля, если он определён, или ``{}`` в противном случае. Если *extraglobs не определён, вместо него используется {}.

Объекты DocTestParser

class doctest.DocTestParser[source]

Класс-обработчик, используемый для извлечения интерактивных примеров из строки и использует их для создания объектов DocTest.

New in version 2.4.

DocTestParser определяет следующие методы:

get_doctest(string, globs, name, filename, lineno)[source]

Извлекает все тесты из заданной строки и собирает их в объект DocTest.

globs, name, filename и lineno являются атрибутами для нового объекта DocTest. Смотрите документацию для DocTest.

get_examples(string[, name])[source]

Извлекает все тесты из заданой строки и возвращает их как список объектов Example. Номера строк начинаются с 0. Не обязательный аргумент name является именем, идентифицирующим строку и он используется только для сообщений об ошибках.

parse(string[, name])[source]

Делит заданную строку на тесты и текст и возвращает их как список из Example и строк. Номера строк для Example начинаются с 0. Не обязательный аргумент name является именем, идентифицирующим строку и он используется только для сообщений об ошибках.

Объекты DocTestRunner

class doctest.DocTestRunner([checker][, verbose][, optionflags])[source]

Класс-обработчик, используемый для извлечения и верификации интерактивных тестов в DocTest.

Сравнение между ожидаемым выводом и реальным производится при помощи OutputChecker. Это сравнение можно настроить при помощи различных флагов, описанных подробно в разделе Опции/флаги. Если флагов не достаточно, то настроить сравнение можно передав подкласс OutputChecker в конструктор.

То, как выполнитель теста отображает вывод, можно определить двумя способами. Первый - функция для вывода может быть передана в TestRunner.run(); эта функция будет вызвана для строки, которая должна быть отображена. По умолчанию это sys.stdout.write. Если этого не достаточно, то можно настроить вывод при помощи подкласса DocTestRunner, и переопределить его методы report_start(), report_success(), report_unexpected_exception() и report_failure().

Не обязательный именованый аргумент checker определяет объект OutputChecker (или его замену), который должен использоваться для сравнения итогового и ожидаемого вывода теста.

Не обязательный именованый аргумент verbose определяет подробность сообщений DocTestRunner‘а. Если verbose равен True, тогда будет выводиться информация о каждом примере в процессе его выполнения. Если verbose равен False, тогда будут выведены только сообщения об ошибках. Если verbose не определено или None, тогда подробный вывод будет выведен в том и только в том случае, если используется опция командной строки -v.

Не обязательный именованный аргумент optionflags может быть использован для управления того, как исполнитель тестов будет сравнивать реальный вывод и ожидаемым и как будут отображаться ошибки. Более подробно это описано в разделе Опции/флаги.

New in version 2.4.

DocTestParser определяет следующие методы:

report_start(out, test, example)[source]

Сообщает, что исполнитель тестов готов выполнить данный пример. Этот метод предоставлен чтобы позволить подклассам DocTestRunner настроить свой вывод. Он не должен вызываться напрямую.

example - пример, который должен быть обработан. test - тест, содержащий example. out - функция вывода информации, которая будет передана в DocTestRunner.run().

report_success(out, test, example, got)[source]

Сообщает, что данный пример был успешно выполнен. Этот метод предоставлен чтобы позволить подклассам DocTestRunner настроить свой вывод. Он не должен вызываться напрямую.

example - пример, который должен быть обработан. got - реальный вывод примера. test - тест, содержащий example. out - функция вывода информации, которая будет передана в DocTestRunner.run().

report_failure(out, test, example, got)[source]

Сообщает, что данный пример был провален. Этот метод предоставлен чтобы позволить подклассам DocTestRunner настроить свой вывод. Он не должен вызываться напрямую.

example - пример, который должен быть обработан. got - реальный вывод примера. test - тест, содержащий example. out - функция вывода информации, которая будет передана в DocTestRunner.run().

report_unexpected_exception(out, test, example, exc_info)[source]

Сообщает, что данный пример вызвал неожиданное исключение. Этот метод предоставлен чтобы позволить подклассам DocTestRunner настроить свой вывод. Он не должен вызываться напрямую.

example - пример, который должен быть обработан. exc_info - кортеж, содержащий информацию о неожиданном исключении (как возвращаемый sys.exc_info()). test - тест, содержащий example. out - функция вывода информации, которая будет передана в DocTestRunner.run().

run(test[, compileflags][, out][, clear_globs])[source]

Запускает пример в test (объект DocTest), и отображает результат при помощи “writer” из функции out.

Примеры запускаются в пространстве имён test.globs. Если clear_globs истинен (по умолчанию), тогда пространство имён будет отчищено после выполнения теста, чтобы помочь со сбором мусора. Если Вы захотите проверить пространство имён после выполнения теста, используйте clear_globs=False.

compileflags даёт набор флагов, которые должны использоваться компилятором Python при выполнении примера. Если не определён, то по умолчанию установлены флаги future-import, которые применяются к globs.

Вывод каждого примера проверяется при помощи проверщика вывода из класса DocTestRunner и результат форматируется методами DocTestRunner.report_*().

summarize([verbose])[source]

Выводит итог всех тестов, которые были запущены этим DocTestRunner и возвращает named tuple TestResults(failed, attempted).

Не обязательный аргумент verbose определяет то, насколько детальна будет итоговая информация. Если “подробность” не определена, тогда используется указатель “подробности” из DocTestRunner.

Changed in version 2.6: Добавлен именованый кортеж.

Объекты OutputChecker

class doctest.OutputChecker[source]

Класс, используемый для проверки совпадения реального вывода с ожидаемым. Он определяет два метода: check_output(), который сравнивает полученную пару выводов и возвращает true, если они совпадают; и output_difference(), который возвращает строку, описывающую разницу между двумя выводами.

New in version 2.4.

OutputChecker определяет следующие методы:

check_output(want, got, optionflags)[source]

Возвращает True тогда и только тогда, когда реальный вывод теста (got) совпадает с желаемым (want). Эти строки всегда будут совпадающими, если они идентичны, но в зависимости от флагов исполнителя тестов некоторые не идентичные строки тоже могут рассматриваться как совпавшие. Более подробно это описано в разделе Опции/флаги.

output_difference(example, got, optionflags)[source]

Возвращает строку, описывающую разницу между ожидаемым выводом для примера (example) и реальным выводом (got). optionflags - набор флагов, используемых для сравнения want и got.

Отладка

Doctest предоставляет несколько механизмов для отладки примеров:

  • Некоторые функции преобразуют doctest`ы в исполняемые программы Python, которые можно запустить в отладчике Python, pdb.

  • Класс DebugRunner является подклассом DocTestRunner, который возбуждает исключение для первого неудачного примера, содержащий информацию об этом примере. Эта информация может быть использована для “посмертной” отладки примера.

  • Наборы unittest, создаваемые функцией DocTestSuite() поддреживают метод debug(), определяемый классом unittest.TestCase.

  • Вы можете добавить вызов функции pdb.set_trace() в примере doctest`а и вы будете “выброшены” в отладчик Python, когда эта строка будет выполнена. После этого вы сможете проверить переменные, и т.д. Например, предположим, что файл a.py содержит только такую строку документации:

    """
    >>> def f(x):
    ...     g(x*2)
    >>> def g(x):
    ...     print x+3
    ...     import pdb; pdb.set_trace()
    >>> f(3)
    9
    """
    

    Тогда интерактивная сессия Python может выглядеть так:

    >>> import a, doctest
    >>> doctest.testmod(a)
    --Return--
    > <doctest a[1]>(3)g()->None
    -> import pdb; pdb.set_trace()
    (Pdb) list
      1     def g(x):
      2         print x+3
      3  ->     import pdb; pdb.set_trace()
    [EOF]
    (Pdb) print x
    6
    (Pdb) step
    --Return--
    > <doctest a[0]>(2)f()->None
    -> g(x*2)
    (Pdb) list
      1     def f(x):
      2  ->     g(x*2)
    [EOF]
    (Pdb) print x
    3
    (Pdb) step
    --Return--
    > <doctest a[2]>(1)?()->None
    -> f(3)
    (Pdb) cont
    (0, 3)
    >>>
    

    Changed in version 2.4: Была добавлена возможность использовать pdb.set_trace() внутри doctest`ов.

Фнукции, которые преобразуют doctest`ы в код Python и, возомжно, запускают полученный код в отладчике:

doctest.script_from_examples(s)[source]

Преобразует текст с примером в скрипт.

Аргумент s является строкой, содержащей пример doctest`а. Строка преобразуется в скрипт Python, где примеры в s преобразуются в обычный код, а всё остальное - в комментарии. Полученный скрипт возвращается как строка. Например:

import doctest
print doctest.script_from_examples(r"""
    Set x and y to 1 and 2.
    >>> x, y = 1, 2

    Print their sum:
    >>> print x+y
    3
""")

выведет:

# Set x and y to 1 and 2.
x, y = 1, 2
#
# Print their sum:
print x+y
# Expected:
## 3

Эта функция используется другими функциями модуля (см ниже), но может быть полезна, если вы хотите преобразовать интерактивную сессию Python в скрипт Python.

New in version 2.4.

doctest.testsource(module, name)[source]

Преобразует doctest из объекта в скрипт.

Аргумент module является объектом модуля или именем модуля с точками, содержащим объект, чьи строки документации нас интересуют. Аргумент name - имя (в модуле) объекта, чьи строки документации нам нужны. Результатом является строка, содержащая строки документации объекта, преобразованные в скрипт Python, как это описано выше в функции script_from_examples(). Например, если модуль a.py содержит функцию верхнего уровня f(), тогда:

import a, doctest
print doctest.testsource(a, "a.f")

выведет скрипт для строки документации функции f(), где тест будет преобразован в код, а всё остальное помещено в комментакрии.

New in version 2.3.

doctest.debug(module, name[, pm])[source]

Выполняет отладку doctest`а для объекта.

Аргументы module и name аналогичны для функции testsource(). Полученный скрипт для строк документации нужного объекта записываются во временный файл и затем этот файл запускается под котролем отладчика Python - pdb.

Поверхностная копия module.__dict__ будет использоваться как для локального, так и для глобального контекста исполнения.

Не обязательный аргумент pm определяет, будет ли использоваться “посмертная” отладка. Если pm истинен, тогда скрпит запускается напрямую и отладчик подключается только если скрипт прерывается возбуждением не обрабатываемого исключения. Если это происходит, включается “посмертная” отладка при помощи pdb.post_mortem(), получая объект трассировки из не перехваченного исключения. Если pm не определено или ложно, скрипт запускается с самого начала под отладчиком, передавая соответствующий вызов функции execfile() в pdb.run().

New in version 2.3.

Changed in version 2.4: Был добавлен аргумент pm.

doctest.debug_src(src[, pm][, globs])[source]

Выполняет отладку doctest`а из строки.

Эта функция похожа на debug(), за исключением того, что строка, содержащая примеры определена напрямую, через аргумент src.

Не обязательный аргумент pm имеет то же значение, что и в функции debug().

Не обязательный аргумент globs передаёт словарь, который будет использоваться как локальный и глобальный контекст исполнения. Если он не определён или равен None, используется пустой словарь. Если определено, будет использоваться поверхностная копия словаря.

New in version 2.4.

Класс DebugRunner и специальные исключения, которые он может вызывать, являются наиболее интересными для авторов тестирующих фреймворков, и будут описаны тут лишь вкратце. Смотрите исходный код и в особенности строки документации DebugRunner‘а (которые являются тестами!), где вы найдёте больше информации:

class doctest.DebugRunner([checker][, verbose][, optionflags])[source]

Подкласс DocTestRunner‘а, который возбуждает исключение, как только обнаруживается ошибка. Если возникает неожиданное исключение, возбуждается исключение UnexpectedException, содержащее тест, пример и оригинальное исключение. Если вывод не совпадает, тогда возбуждается исключение DocTestFailure, содержащее тест, пример и реальный вывод.

Информацию о параметрах конструктора и методах можно найти в документации к классу DocTestRunner в разделе Продвинутый API.

Есть два исключения, которые могут быть вызваны экземпляром DebugRunner:

exception doctest.DocTestFailure(test, example, got)[source]

Исключение возбуждается классом DocTestRunner, чтобы сообщить, что реальный вывод примера не соответствует ожидаемому. Аргументы конструктора используются для инициализации атрибутов с тем же именем.

DocTestFailure определяет следующие атрибуты:

DocTestFailure.test

Объект DocTest, который выполнялся, когда провалился пример.

DocTestFailure.example

Example, который был провален.

DocTestFailure.got

Реальный вывод примера.

exception doctest.UnexpectedException(test, example, exc_info)[source]

Исключение, вызываемое DocTestRunner, чтобы сообщить, что пример doctest`а вызвал неожиданное исключение. Аргументы конструктора используются для инициализации атрибутов с тем же именем.

UnexpectedException определяет следующие атрибуты:

UnexpectedException.test

Объект DocTest, который выполнялся, когда пример был провален.

UnexpectedException.example

Example, который был провален.

UnexpectedException.exc_info

Кортеж, содержащий информацию о неожиданном исключении в том виде, как он возвращается функцией sys.exc_info().

Soapbox

Как упоминалось в предисловии, doctest был создан для трёх основных целей:

  1. Проверить примеры в строках документации.
  2. Регрессионного тестирования.
  3. Исполняемой документации / читабельных тестов.

Эти варианты использования имеют различные требования и очень важно их различать. В частности, заполнение ваших строк документации не ясными тестами испортит вашу документацию.

Когда вы пишете строки документации, тщательно выбирайте примеры для них. Это отдельное искуство, которое может быть не очевидно на первый взгляд. Примеры должны добавлять ценности документации. Хорошие примеры могут быть ценее многих слов. Если все сделано внимательно, примеры будут иметь неоценимое значение для пользователей и окупят время, необходимое, чтобы собрать их; особенно с течением времени. Я до сих пор удивляюсь, как часто один из моих примеров doctest перестает работать после «безвредного» изменения.

Doctest так же является замечательным инструментом для регрессионного тестирования, особенно если вы не экономите на пояснительном тексте. Смешивание текста и примеров позволяет гораздо проще отслеживать то, что конкретно тестируется и почему. Когда тест не удаётся, хороший текст позволяет гораздо проще понять, в чём именно проблема и как её исправить. Конечно, вы можете написать исчерпывающие комментарии в код теста, но мало кто так делает. Большинство обнаруживает, что использование docstest’a вместо этого приводит к более понятным тестам. Возможно это просто потому, что doctest позволяет писать текст проще, чем код, так как указывать комментарии в коде теста достаточно тяжело. Я думаю, что на самом деле это глубже: когда вы пишете тесты, вы хотите объяснить суть вашего скрипта и проиллюстрировать его примерами. Это само по себе ведёт к тому, что тестовые файлы начинаются с простых примеров и переходят к более сложным. В результате вы получаете последовательное изложение вместо набора изолированных функций, которые проверяют отдельные куски функционала, что выглядит гораздо менее понятно. Такой подход стирает разницу между тестированием и объяснением.

Регрессионное тестирование лучше всего ограничивается отдельными объектами и файлами. Есть несколько опций для организации тестов:

  • Написание текстовых файлов, содержащих тесты и примеры, которые тестируют файлы при помощи testfile() или DocFileSuite(). Это рекомендуется, хотя проще это сделать с новым проектом, который с самого начала расчитан на использование doctest.
  • Определить функции с названием _regrtest_topic, которые содержат одиночные строки документации, содержащие тесты для нужного топика. Эти функции могут находиться в том же файле, что и модуль, или быть вынесены в отдельный тестовый файл.
  • Определить словарь __test__, отображающий темы регрессионного тестирования на строки документации, содержащие тесты.

Footnotes

[1]Примеры, содержащие как ожидаемый вывод, так и исключения не поддерживаются. Попытка угадать, где заканчивается одно и начинается другое способствует ошибкам и это делает тесты менее понятными.