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


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

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

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

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

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

>>> factorial(5)
120
"""

def factorial(n):
    """Вернуть факториал n, целого числа >= 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

    Факториалы чисел с плавающей запятой допустимы, но число с плавающей запятой должно быть точным целым числом.:
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
    >>> factorial(30.0)
    265252859812191058636308480000000

    Оно также не должно быть смехотворно большим:
    >>> 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 напечатает подробный log своих попыток, а в конце напечатает сводку:

$ 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.

Простое использование: проверка примеров в строках документации

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

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

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

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

python M.py

Если пример успешно выполнится он ничего не отобразит, а в случае неудачи пример(ы) и причина(ы) ошибки выводятся на стандартный вывод, а последняя строка вывода – ***Test Failed*** N failures., где N – это количество неудачных примеров.

Вместо этого запустить его с ключом -v:

python M.py -v

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

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

Существует также ярлык командной строки для запуска testmod(). Вы можете указать интерпретатору Python запустить модуль доктест непосредственно из стандартной библиотеки и передать имя модуля в командной строке:

python -m doctest -v example.py

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

Дополнительные сведения о testmod() см. в разделе Базовый API.

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

Другое простое применение доктест — тестирование интерактивных примеров в текстовом файле. Это можно сделать с помощью функции 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.

Существует также ярлык командной строки для запуска testfile(). Вы можете указать интерпретатору Python запустить модуль доктест непосредственно из стандартной библиотеки и передать имена файлов в командной строке:

python -m doctest -v example.txt

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

Дополнительные сведения о testfile() см. в разделе Базовый API.

Как это устроено?

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

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

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

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

<name of M>.__test__.K

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

Детали реализации CPython: До версии 3.4 модули расширения, написанные на C, не полностью обрабатывались doctestом.

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

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

>>> # комментарии игнорируются
>>> x = 12
>>> x
12
>>> if x == 13:
...     print("да")
... else:
...     print("нет")
...     print("Нет")
...     print("НЕТ!!!")
...
нет
Нет
НЕТ!!!
>>>

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

Мелкий шрифт:

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

  • Все символы жёсткой табуляции заменяются пробелами с использованием 8-колоночных позиций табуляции. Табы в выходных данных, сгенерированных тестируемым кодом, не изменяются. Поскольку любые жёсткие табы в образце вывода расширены, это означает, что если вывод кода включает жёсткие табы, единственный способ передать доктест — это если действует параметр NORMALIZE_WHITESPACE или директива @1 @1. В качестве альтернативы тест можно переписать, чтобы захватить выходные данные и сравнить их с ожидаемым значением как часть теста. Такая обработка табов в исходном коде была достигнута методом проб и ошибок и оказалась наименее подверженной ошибкам способом их обработки. Можно использовать другой алгоритм для обработки табов, написав собственный класс DocTestParser.

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

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

    >>> def f(x):
    ...     r'''Обратная косая черта в необработанном докстринге: m\n'''
    >>> print(f.__doc__)
    Обратная косая черта в необработанном докстринге: m\n
    

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

    >>> def f(x):
    ...     '''Обратная косая черта в необработанном докстринге: m\\n'''
    >>> print(f.__doc__)
    Обратная косая черта в необработанном докстринге: m\n
    
  • Начальная колонка не имеет значения:

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

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

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

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

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

Как насчёт исключений?

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

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

>>> [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 (most recent call last):
Traceback (innermost last):

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

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

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

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

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

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

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

Некоторые детали вы должны прочитать один раз, но не нужно будет запоминать:

  • Doctest не может угадать, был ли получен ожидаемый результат от обратной трассировки исключений или от обычной печати. Так, например, пример, который ожидает, что ValueError: 42 is prime пройдет, независимо от того, действительно ли вызвано ValueError или если пример просто печатает данный текст трассировки. На практике обычный вывод редко начинается со строки заголовка трассировки, так что это не создаёт реальных проблем.
  • Каждая строка стека трассировки (если она есть) должна иметь отступ больше, чем первая строка в примере, или начинается с небуквенно-цифрового символа. Первая строка, следующая за заголовком трассировки с таким же отступом и начинающаяся с буквенно-цифрового символа, считается началом сведений об исключении. Конечно, это правильно для подлинной трассировки.
  • Если указан параметр IGNORE_EXCEPTION_DETAIL доктест, всё, что следует за крайним левым двоеточием, и любая информация о модуле в имени исключения игнорируется.
  • Интерактивная оболочка пропускает строку заголовка трассировки для некоторых SyntaxError. Но доктест использует строку заголовка трассировки, чтобы отличать исключения от неисключений. Таким образом, в редких случаях, когда вам нужно протестировать 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
    

Флаги опций

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

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

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

doctest.DONT_ACCEPT_TRUE_FOR_1

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

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 отчетах Python 3. Следовательно, оба данные варианта будут работать с указанным флагом, независимо от того, выполняется ли тест в Python 2.7 или Python 3.2 (или более поздних версиях):

>>> 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 или более ранних версиях (данные релизы не поддерживают директивы доктеста и игнорируют их как нерелевантные комментарии). Например:

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

проходит в Python 2.3 и более поздних версиях Python с указанным флагом, даже несмотря на то, что детали изменились в Python 2.4, чтобы сказать «не делает» вместо «не делает».

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

doctest.SKIP

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

Флаг SKIP также можно использовать для временного «закомментирования» примеров.

doctest.COMPARISON_FLAGS

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

Вторая группа параметров управляет тем, как сообщается о неудачных тестах:

doctest.REPORT_UDIFF

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

doctest.REPORT_CDIFF

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

doctest.REPORT_NDIFF

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

doctest.REPORT_ONLY_FIRST_FAILURE

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

doctest.FAIL_FAST

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

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

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

doctest.REPORTING_FLAGS

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

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

doctest.register_optionflag(name)

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

MY_FLAG = register_optionflag('MY_FLAG')

Директивы

Doctest директивы могут использоваться для изменения флагов параметра для отдельного примера. Директивы Doctest — это специальные Python комментарии, следующие за исходным кодом примера:

doctest               ::=  «#» «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(list(range(20))) 
[0,   1,  2,  3,  4,  5,  6,  7,  8,  9,
10,  11, 12, 13, 14, 15, 16, 17, 18, 19]

Без этой директивы потерпит неудачу как потому, что фактический вывод не содержит двух пробелов перед однозначными элементами списка, так и потому, что фактический вывод находится в одной строке. Данный тест также проходит, и для этого также требуется директива:

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

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

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

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

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

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

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

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

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

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

>>> foo()
{"Hermione", "Harry"}

уязвим! Один обходной путь — сделать

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

вместо. Другое вариант, сделать:

>>> d = sorted(foo())
>>> d
['Harry', 'Hermione']

Примечание

До Python 3.6 при печати словаря Python не гарантировал, что пары ключ- значение будут напечатаны в каком-либо порядке.

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

Ещё одна плохая идея — печатать то, что содержит адрес объекта, например

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

Директива ELLIPSIS дает хороший подход к последнему примеру:

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

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

>>> 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.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 задаёт словарь, который будет использоваться в качестве глобальных переменных при выполнении примеров. Для доктестов создаётся новая поверхностная копия этого словаря, поэтому его примеры начинаются с чистого листа. По умолчанию или если None, используется новый пустой словарь.

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

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

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

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

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

Необязательный аргумент 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, должны быть указаны в форме ключевого аргумента.

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

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

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

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

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

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

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

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

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

Unittest API

По мере роста вашей коллекции модулей с доктест вам понадобится способ систематического запуска всех доктестов. 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)

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

Возвращённый unittest.TestSuite должен запускаться инфраструктурой модульного тестирования и запускать интерактивные примеры в каждом файле. Если пример в каком-либо файле дает сбой, то синтезированный модульный тест завершается с ошибкой, и возникает исключение 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 задаёт параметры доктест по умолчанию для тестов, созданные путём объединения флагов отдельных параметров. См. раздел Флаги опций. См. функцию 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)

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

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

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

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

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

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

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

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

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

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

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

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

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

doctest.set_unittest_reportflags(flags)

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

Аргумент flags принимает побитовое ИЛИ опционных флагов. См. раздел Флаги опций. Можно использовать только «отчётные флаги».

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

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

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

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

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

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

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

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

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

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

Объекты DocTest

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

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

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

examples

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

globs

Пространство имён (иначе глобальные), в котором должны запускаться примеры. Это словарь, отображающий имена в значения. Любые изменения пространства имён, сделанные в примерах (например, привязка новых переменных), будут отражены в 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

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

exc_msg

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

lineno

Номер строки в строке, содержащей данный пример, с которой он начинается. Данный номер строки отсчитывается от нуля по отношению к началу содержащей строки.

indent

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

options

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

Объекты DocTestFinder

class doctest.DocTestFinder(verbose=False, parser=DocTestParser(), recurse=True, exclude_empty=True)

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Чтобы помочь найти номер строки объекта в его файле.

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

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

Объекты DocTestParser

class doctest.DocTestParser

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

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

get_doctest(string, globs, name, filename, lineno)

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

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

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

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

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

Разделяет данную строку на примеры и промежуточный текст и возвращает их в виде списка чередующихся Example и строк. Номера строк для Example начинаются с 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 (или вставную замену), который следует использовать для сравнения ожидаемых выходных данных с фактическими выходными данными примеров доктестов.

Необязательный ключевой аргумент 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) и отобразить результаты с помощью функции записи out.

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

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

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

summarize(verbose=None)

Распечатать сводку всех тестовых случаев, запущенных DocTestRunner, и возвращает именованный кортеж TestResults(failed, attempted).

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

Объекты OutputChecker

class doctest.OutputChecker

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

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

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

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

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

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

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

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

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

doctest.script_from_examples(s)

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

Аргумент s — это строка, содержащая доктест примеры. Строка преобразуется в скрипт Python, где доктест примеры в 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 — это имя (внутри модуля) объекта с интересующим доктестом. Результатом является строка, содержащая строку документации объекта, преобразованную в сценарий Python, как приведено для script_from_examples() выше. Например, если модуль a.py содержит функцию верхнего уровня f(), то

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

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

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

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

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

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

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

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

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

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

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

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

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

class doctest.DebugRunner(checker=None, verbose=None, optionflags=0)

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

Сведения о параметрах и методах конструктора см. в документации для DocTestRunner в разделе Расширенный API.

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

exception doctest.DocTestFailure(test, example, got)

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

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

DocTestFailure.test

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

DocTestFailure.example

Ошибка Example.

DocTestFailure.got

Фактический результат примера.

exception doctest.UnexpectedException(test, example, exc_info)

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

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

UnexpectedException.test

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

UnexpectedException.example

Ошибка Example.

UnexpectedException.exc_info

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

Мыльница

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

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

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

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

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

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

  • Напишите текстовые файлы, содержащие тестовые примеры, в качестве интерактивных примеров, и протестируйте файлы, используя testfile() или DocFileSuite(). Это рекомендуется, хотя это проще всего сделать для новых проектов, изначально разработанных для доктест использования.
  • Определите функции с именем _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]Примеры, содержащие как ожидаемый вывод, так и исключение, не поддерживаются. Попытка угадать, где заканчивается одно и начинается другое, слишком подвержена ошибкам, и это также делает тест запутанным.