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 есть много примеров доктестов. Особенно полезные примеры можно найти в стандартном тестовом файле Lib/test/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, передав 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. Содержимое файла рассматривается так, как если бы это была одна гигантская doc-строка; файл не обязательно должен содержать программу на 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 на этих примерах, читайте в следующих разделах.

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

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

Кроме того, если M.__test__ существует и «является истиной», он должен быть диктой, и каждая запись сопоставляет (строковое) имя с объектом функции, объектом класса или строкой. Докстринги функций и объектов классов, найденные из M.__test__, перебираются, а строки обрабатываются так, как если бы они были докстрингами. При выводе ключ K в M.__test__ появляется с именем

<name of M>.__test__.K

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

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

В большинстве случаев копирование и вставка интерактивной консольной сессии работает хорошо, но 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-колоночные упоры табуляции. Табуляции в выводе, генерируемом тестируемым кодом, не изменяются. Поскольку все символы табуляции в выводе примера расширяются, это означает, что если вывод кода включает символы табуляции, тест может пройти только в том случае, если действует опция NORMALIZE_WHITESPACE или directive. В качестве альтернативы, тест можно переписать так, чтобы перехватить вывод и сравнить его с ожидаемым значением как часть теста. Такой способ работы с табуляциями в исходном тексте был найден методом проб и ошибок и оказался наименее подверженным ошибкам. Можно использовать другой алгоритм обработки табуляции, написав собственный класс DocTestParser.

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

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

    >>> 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, и имена, определенные ранее в выполняемой doc-строке. Примеры не могут видеть имена, определенные в других документах.

Вы можете принудительно использовать свой собственный dict в качестве контекста выполнения, передавая 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

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

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

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

За заголовком traceback следует необязательный стек traceback, содержимое которого игнорируется 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 или просто выведен текст трассировки. На практике обычный вывод редко начинается со строки заголовка traceback, поэтому это не создает реальных проблем.

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

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

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

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

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

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

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

Флаги опций

Ряд опциональных флагов управляет различными аспектами поведения doctest. Символические имена флагов предоставляются в виде констант модуля, которые можно bitwise ORed собрать вместе и передать различным функциям. Имена также могут быть использованы в doctest directives и могут быть переданы интерфейсу командной строки doctest через опцию -o.

Добавлено в версии 3.4: Опция командной строки -o.

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

doctest.DONT_ACCEPT_TRUE_FOR_1

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

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. Он также проигнорирует любое полное имя, включенное перед классом исключения, которое может отличаться в разных реализациях и версиях 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

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

doctest.REPORT_CDIFF

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

doctest.REPORT_NDIFF

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

doctest.REPORT_ONLY_FIRST_FAILURE

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

doctest.FAIL_FAST

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

Командная строка doctest принимает опцию -f в качестве сокращения для -o FAIL_FAST.

Добавлено в версии 3.4.

doctest.REPORTING_FLAGS

Битовая маска или объединение всех вышеперечисленных флагов отчетности.

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

doctest.register_optionflag(name)

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

MY_FLAG = register_optionflag('MY_FLAG')

Директивы

Директивы Doctest могут быть использованы для изменения option flags для отдельного примера. Директивы 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 серьезно относится к требованию точного совпадения в ожидаемом выводе. Если хотя бы один символ не совпадает, тест проваливается. Возможно, это несколько раз удивит вас, когда вы узнаете, что именно 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 платформы для форматирования плавающих чисел, а библиотеки C сильно различаются по качеству.

>>> 1./7  # risky
0.14285714285714285
>>> print(1./7) # safer
0.142857142857
>>> print(round(1./7, 6)) # much safer
0.142857

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

>>> 3./4  # utterly safe
0.75

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

Базовый API

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

doctest.testfile(filename, module_relative=True, name=None, package=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, parser=DocTestParser(), encoding=None)

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

Протестировать примеры в файле с именем filename. Возврат (failure_count, test_count).

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

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

  • Если module_relative равно False, то filename указывает путь, специфичный для ОС. Путь может быть абсолютным или относительным; относительные пути определяются относительно текущего рабочего каталога.

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

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

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

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

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

Необязательный аргумент report печатает сводку в конце, если true, иначе в конце ничего не печатается. В режиме verbose сводка подробная, в противном случае сводка очень краткая (фактически пустая, если все тесты пройдены).

Необязательный аргумент optionflags (значение по умолчанию 0) принимает bitwise OR флагов опций. См. раздел Флаги опций.

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

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

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

doctest.testmod(m=None, name=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False)

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

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

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

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

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

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

Необязательный аргумент exclude_empty по умолчанию имеет значение false. Если значение равно true, то объекты, для которых не найдено ни одного доктеста, исключаются из рассмотрения. Значение по умолчанию - это хак обратной совместимости, так что код, все еще использующий 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() выше.

Unittest API

По мере роста вашей коллекции doctest’s ed модулей, вам понадобится способ систематического запуска всех их доктестов. 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=True, package=None, setUp=None, tearDown=None, globs=None, optionflags=0, parser=DocTestParser(), encoding=None)

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

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

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

Опции могут быть предоставлены в качестве аргументов ключевых слов:

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

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

  • Если module_relative равно False, то каждое имя файла в paths указывает путь, специфичный для ОС. Путь может быть абсолютным или относительным; относительные пути определяются относительно текущего рабочего каталога.

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

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

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

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

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

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

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

Глобал __file__ добавляется к глобалам, предоставляемым доктестам, загруженным из текстового файла с помощью DocFileSuite().

doctest.DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, setUp=None, tearDown=None, checker=None)

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

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

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

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

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

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

Дополнительные аргументы setUp, tearDown и optionflags такие же, как и для функции DocFileSuite() выше.

Эта функция использует ту же технику поиска, что и testmod().

Изменено в версии 3.5: DocTestSuite() возвращает пустой unittest.TestSuite, если module не содержит docstrings, вместо того, чтобы поднимать ValueError.

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

Аналогично, DocFileSuite() создает unittest.TestSuite из экземпляров doctest.DocFileCase, а DocFileCase является подклассом DocTestCase.

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

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

doctest.set_unittest_reportflags(flags)

Установите флаги отчетности doctest для использования.

Аргумент flags принимает bitwise OR флагов опций. См. раздел Флаги опций. Можно использовать только «флаги отчетности».

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

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

Расширенный API

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

Расширенный API вращается вокруг двух контейнерных классов, которые используются для хранения интерактивных примеров, извлеченных из кейсов doctest:

  • Example: Одиночный Python statement, в паре с ожидаемым выводом.

  • DocTest: Коллекция Examples, обычно извлекаемая из одной строки документа или текстового файла.

Определены дополнительные классы обработки для поиска, разбора и запуска, а также проверки примеров doctest:

  • DocTestFinder: Находит все docstrings в данном модуле и использует DocTestParser для создания DocTest из каждой docstring, которая содержит интерактивные примеры.

  • DocTestParser: Создает объект DocTest из строки (например, docstring объекта).

  • 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

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

name

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

filename

Имя файла, из которого был извлечен данный DocTest; или None, если имя файла неизвестно, или если DocTest не был извлечен из файла.

lineno

Номер строки в filename, с которой начинается данный DocTest, или None, если номер строки недоступен. Номер строки равен нулю по отношению к началу файла.

docstring

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

Примеры объектов

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)

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

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

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

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

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

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

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

Возвращает список DocTests, которые определены docstring’ом obj или docstring’ами любого из содержащихся в нем объектов.

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

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

  • В качестве пространства имен по умолчанию, если не указано globs.

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

  • Чтобы найти имя файла, содержащего объект.

  • Помогает найти номер строки объекта в его файле.

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

Словарь globals для каждого DocTest формируется путем объединения globs и extraglobs (привязки в extraglobs переопределяют привязки в globs). Для каждого DocTest создается новая неглубокая копия словаря globals. Если 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. Номера строк основаны на 0. Необязательный аргумент name является именем, идентифицирующим данную строку, и используется только для сообщений об ошибках.

parse(string, name='<string>')

Разделите заданную строку на примеры и промежуточный текст и верните их в виде списка чередующихся Examples и строк. Номера строк для Examples основаны на 0. Необязательный аргумент 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 можно использовать для управления тем, как программа запуска тестов сравнивает ожидаемый результат с фактическим, и как она отображает ошибки. Для получения дополнительной информации см. раздел Флаги опций.

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

report_start(out, test, example)

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

example - это пример, который будет обработан. test - это тест, содержащий пример. 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) и выведите результаты с помощью функции writer out.

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

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

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

summarize(verbose=None)

Выводит сводку всех тестовых случаев, которые были запущены этим DocTestRunner, и возвращает named tuple 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:

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

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

  • Случаи unittest, генерируемые DocTestSuite(), поддерживают метод debug(), определенный unittest.TestCase.

  • Вы можете добавить вызов pdb.set_trace() в пример doctest, и вы попадете в отладчик Python, когда эта строка будет выполнена. Затем вы можете проверить текущие значения переменных и так далее. Например, предположим, что a.py содержит только этот модуль docstring:

    """
    >>> 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)
    >>>
    

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

doctest.script_from_examples(s)

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

Аргумент s - это строка, содержащая примеры доктестов. Строка преобразуется в сценарий 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)

Преобразование теста для объекта в сценарий.

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

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

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

doctest.debug(module, name, pm=False)

Отладка доктестов для объекта.

Аргументы модуль и имя такие же, как и для функции testsource() выше. Синтезированный сценарий Python для docstring именованного объекта записывается во временный файл, а затем этот файл запускается под управлением отладчика Python, pdb.

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

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

doctest.debug_src(src, pm=False, globs=None)

Отладка доктестов в строке.

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

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

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

Класс DebugRunner и специальные исключения, которые он может вызывать, представляют наибольший интерес для авторов тестовых фреймворков, и здесь мы ограничимся лишь кратким описанием. Более подробную информацию смотрите в исходном коде, и особенно в docstring 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 имеет три основных применения:

  1. Проверка примеров в документах.

  2. Регрессионное тестирование.

  3. Исполняемая документация / грамотное тестирование.

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

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

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

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

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

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

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

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

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))

Сноски

1

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

Вернуться на верх