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 test in __main__
   6 tests in __main__.factorial
7 tests in 2 items.
7 passed.
Test passed.
$

Это все, что вам нужно знать, чтобы начать продуктивно использовать doctest! Перейти. Следующие разделы содержат подробную информацию. Обратите внимание, что в стандартном наборе тестов и библиотеках Python есть множество примеров доктестов. Особенно полезные примеры можно найти в стандартном тестовом файле Lib/test/test_doctest/test_doctest.py.

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

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

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

doctest, а затем просматривает строки документов в модуле M.

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

python M.py

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

Запустите его с помощью переключателя -v:

python M.py -v

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

Вы можете включить режим verbose, передав 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 на этих примерах, читайте в следующих разделах.

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

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

Кроме того, бывают случаи, когда вы хотите, чтобы тесты были частью модуля, но не частью текста справки, что требует, чтобы тесты не были включены в docstring. Doctest ищет переменную уровня модуля под названием __test__ и использует ее для поиска других тестов. Если M.__test__ существует, она должна быть dict, и каждая запись сопоставляет (строковое) имя с объектом функции, объектом класса или строкой. Из M.__test__ выполняется поиск по документам функций и объектов классов, а строки обрабатываются так, как если бы они были документами. В результате ключ K в M.__test__ появляется с именем M.__test__.K.

Например, разместите этот блок кода в верхней части example.py:

__test__ = {
    'numbers': """
>>> factorial(6)
720

>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
"""
}

Значение example.__test__["numbers"] будет рассматриваться как документ-строка, и все тесты в ней будут запущены. Важно отметить, что значение может быть сопоставлено с функцией, объектом класса или модулем; в этом случае doctest рекурсивно ищет в них док-строки, которые затем проверяются на наличие тестов.

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

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

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

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

    >>> 1 + None
      File "<stdin>", line 1
        1 + None
        ~~^~~~~~
    TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
    

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

    >>> 1 + None
      File "<stdin>", line 1
        1 + None
        ^~~~~~~~
    TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
    

Флаги опций

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

Added in version 3.4: Опция командной строки -o.

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

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

Added in version 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()
{"spam", "eggs"}

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

>>> foo() == {"spam", "eggs"}
True

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

>>> d = sorted(foo())
>>> d
['eggs', 'spam']

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

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

>>> id(1.0)  # certain to fail some of the time  
7948648
>>> class C: pass
>>> C()  # the default repr() for instances embeds an address   
<C object at 0x00AC18F0>

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

>>> C()  # doctest: +ELLIPSIS
<C object at 0x...>

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

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

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

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

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

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

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

Также проверяются примеры, достижимые из dict m.__test__, если он существует. 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’овских модулей вам понадобится способ систематического выполнения всех их doctest’ов. doctest предоставляет две функции, которые можно использовать для создания unittest тестовых наборов из модулей и текстовых файлов, содержащих доктесты. Чтобы интегрироваться с unittest, включите функцию load_tests в свой тестовый модуль:

import unittest
import doctest
import my_module_with_doctests

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

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

doctest.DocFileSuite(*paths, module_relative=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, optionflags=0, checker=None)

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

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

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

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

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

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

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

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

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

exception doctest.failureException

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

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

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

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

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

doctest.set_unittest_reportflags(flags)

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

Аргумент flags принимает 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: Находит все документограммы в данном модуле и использует DocTestParser для создания DocTest из каждой документограммы, содержащей интерактивные примеры.

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

Объекты DocTestFinder

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

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

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

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

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

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

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

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

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

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

Объекты TestResults

class doctest.TestResults(failed, attempted)
failed

Количество неудачных тестов.

attempted

Количество попыток прохождения тестов.

skipped

Количество пропущенных тестов.

Added in version 3.13.

Объекты DocTestRunner

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

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

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

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

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

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

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

Прогонщик тестов накапливает статистику. Атрибуты tries, failures и skips позволяют узнать суммарное количество попыток, неудач и пропущенных примеров. Методы run() и summarize() возвращают экземпляр TestResults.

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

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. Возвращается экземпляр TestResults.

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

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

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

summarize(verbose=None)

Выведите сводку всех тестовых случаев, которые были запущены этим DocTestRunner, и верните экземпляр TestResults.

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

DocTestParser имеет следующие атрибуты:

tries

Количество примеров попыток.

failures

Количество неудачных примеров.

skips

Количество пропущенных примеров.

Added in version 3.13.

Объекты 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"))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

exception doctest.DocTestFailure(test, example, got)

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

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

DocTestFailure.test

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

DocTestFailure.example

Неисправный Example.

DocTestFailure.got

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

exception doctest.UnexpectedException(test, example, exc_info)

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

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

UnexpectedException.test

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

UnexpectedException.example

Неисправный Example.

UnexpectedException.exc_info

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

Мыльница

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

  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(f"{fail} failures out of {total} tests")

Сноски