Spec-Zone.ru › Python 3.13

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 много примеров doctests. Особенно полезные примеры можно найти в стандартном тестовом файле Lib/test/test_doctest/test_doctest.py.

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

Самый простой способ начать использовать doctest (но не обязательно так вы и будете продолжать) — помещать в каждый модуль 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 запустить модуль doctest напрямую из стандартной библиотеки и передать имя(на) модуля(ей) в командной строке:

python -m doctest -v example.py

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

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

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

Ещё одно простое применение doctest — тестирование интерактивных примеров в текстовом файле. Это можно сделать с помощью функции testfile():

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

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

The ``example`` module
======================

Using ``factorial``
-------------------

This is an example text file in reStructuredText format.  First import
``factorial`` from the ``example`` module:

    >>> from example import factorial

Now use it:

    >>> factorial(6)
    120

Запуск doctest.testfile("example.txt") затем обнаруживает ошибку в этой документации:

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

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

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

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

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

python -m doctest -v example.txt

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

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

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

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

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

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

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

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

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

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

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

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

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

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

>>> # comments are ignored
>>> x = 12
>>> x
12
>>> if x == 13:
...     print("yes")
... else:
...     print("no")
...     print("NO")
...     print("NO!!!")
...
no
NO
NO!!!
>>>

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

Мелкие детали:

  • Ожидаемый вывод не может содержать строку, состоящую только из пробелов, так как такая строка считается сигналом окончания ожидаемого вывода. Если ожидаемый вывод содержит пустую строку, поместите <BLANKLINE> в свой пример doctest каждый раз, когда ожидается пустая строка.
  • Все символы табуляции расширяются до пробелов, используя 8-колонные позиции табуляции. Табуляции в выводе, сгенерированном тестируемым кодом, не изменяются. Поскольку любые жесткие табуляции в образце вывода расширяются, это означает, что если вывод кода содержит жесткие табуляции, единственный способ, которым doctest может пройти, это если опция NORMALIZE_WHITESPACE или директива активна. В качестве альтернативы тест можно переписать, чтобы захватить вывод и сравнить его с ожидаемым значением в рамках теста. Такое обработка табуляции в исходном коде была получена методом проб и ошибок и оказалась наименее подверженной ошибкам. Можно использовать другой алгоритм обработки табуляции, написав пользовательский класс DocTestParser.
  • Вывод в стандартный вывод (stdout) захватывается, но не вывод в стандартный поток ошибок (stderr) (обработка исключений захватывается с помощью другого метода).
  • Если вы продолжаете строку с помощью обратного слэша в интерактивной сессии или по любой другой причине используете обратный слэш, вам следует использовать сырую строку, которая сохранит ваши обратные слэши точно так, как вы их ввели:

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

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

    >>> def f(x):
    ...     '''Backslashes in a raw docstring: m\\n'''
    ...
    >>> print(f.__doc__)
    Backslashes in a raw docstring: m\n
    
  • Начальная колонка не имеет значения:

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

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

Какой контекст выполнения используется?

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

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

Что насчет исключений?

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

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

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

Этот doctest выполняется успешно, если возбуждено ValueError с деталью list.remove(x): x not in list в виде показанной.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Флаги параметров

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

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

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

doctest.DONT_ACCEPT_TRUE_FOR_1

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

doctest.DONT_ACCEPT_BLANKLINE

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

doctest.NORMALIZE_WHITESPACE

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

doctest.ELLIPSIS

При указании маркер многоточия (...) в ожидаемом выводе может соответствовать любому подстроке в фактическом выводе. Это включает подстроки, которые охватывают границы строк, и пустые подстроки, поэтому лучше всего сохранять простоту использования. Сложные применения могут привести к тем же неожиданностям «опс, он совпал слишком много!», что и .* в регулярных выражениях.

doctest.IGNORE_EXCEPTION_DETAIL

При указании doctest, ожидающие исключения, проходят, если возникает исключение нужного типа, даже если детали (сообщение и полное имя исключения) не совпадают.

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

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

>>> raise Exception('message')
Traceback (most recent call last):
builtins.Exception: message

>>> raise Exception('message')
Traceback (most recent call last):
__main__.Exception: message

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

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

doctest.SKIP

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

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

doctest.COMPARISON_FLAGS

Побитовая маска, объединяющая все флаги сравнения выше.

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

doctest.REPORT_UDIFF

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

doctest.REPORT_CDIFF

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

doctest.REPORT_NDIFF

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

doctest.REPORT_ONLY_FIRST_FAILURE

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

doctest.FAIL_FAST

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

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

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

doctest.REPORTING_FLAGS

Побитовая маска, объединяющая все флаги отчетов выше.

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

doctest.register_optionflag(name)

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

MY_FLAG = register_optionflag('MY_FLAG')

Директивы

Директивы doctest могут использоваться для изменения флагов параметров для отдельного примера. Директивы 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]

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

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

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

>>> 3./4  # utterly safe
0.75

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

Основные API

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

doctest.run_docstring_examples(f, globs, verbose=False, name='NoName', compileflags=None, optionflags=0)

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

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

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

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

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

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

END_OF_DOCUMENT_MARKER

API модуля Unittest

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

import unittest
import doctest
import my_module_with_doctests

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

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

doctest.DocFileSuite(*paths, module_relative=True, package=None, setUp=None, tearDown=None, globs=None, optionflags=0, parser=DocTestParser(), encoding=None)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

exception doctest.failureException

При сбоях doctest, которые были преобразованы в тесты unittest с помощью 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 принимает побитовое ИЛИ флагов опций. Смотрите раздел Флаги опций. Можно использовать только «флаги отчета».

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

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

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

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

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

  • Example: Один Python-выражение, сопоставленный с ожидаемым выводом.
  • DocTest: Коллекция Exampleов, как правило, извлекаемых из одного docstring или текстового файла.

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

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

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

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

Объекты DocTest

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

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

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

examples

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

globs

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

name

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

filename

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

lineno

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

docstring

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

Объекты Example

class doctest.Example(source, want, exc_msg=None, lineno=0, indent=0, options=None)

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

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

source

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

want

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

exc_msg

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

lineno

Номер строки в строке, содержащей этот пример, где начинается пример. Номер строки нулевой относительно начала содержащей строки.

indent

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

options

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

END_OF_DOCUMENT_MARKER

Объекты DocTestFinder

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Объекты DocTestParser

class doctest.DocTestParser

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

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

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

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

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

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

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

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

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

Объекты TestResults

class doctest.TestResults(failed, attempted)
failed

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

attempted

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

skipped

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

Добавлен в версии 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 — тест, содержащий example. out — функция вывода, переданная в DocTestRunner.run().

report_success(out, test, example, got)

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

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

report_failure(out, test, example, got)

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

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

report_unexpected_exception(out, test, example, exc_info)

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

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

run(test, compileflags=None, out=None, clear_globs=True)

Выполнить примеры в test (объект DocTest) и отобразить результаты с помощью функции записи out. Вернуть экземпляр TestResults.

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

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

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

summarize(verbose=None)

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

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

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

tries

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

failures

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

skips

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

Добавлен в версии 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:

  • Несколько функций преобразуют doctest в исполняемые программы Python, которые можно запустить под отладчиком Python, pdb.
  • Класс DebugRunner является подклассом DocTestRunner и вызывает исключение для первого не пройденного примера, содержащего информацию об этом примере. Эта информация может быть использована для проведения постобработки отладки примера.
  • Примеры unittest, сгенерированные с помощью DocTestSuite(), поддерживают метод debug(), определённый в unittest.TestCase.
  • Вы можете добавить вызов pdb.set_trace() в пример doctest, и вы перейдете к отладчику Python, когда эта строка будет выполнена. Затем вы можете проверить текущие значения переменных и так далее. Например, предположим, что a.py содержит только этот строковый документ модуля:

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

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

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

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

doctest.script_from_examples(s)

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

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

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

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

отображает:

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

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

doctest.testsource(module, name)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

exception doctest.DocTestFailure(test, example, got)

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

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

DocTestFailure.test

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

DocTestFailure.example

Пример Example, который завершился ошибкой.

DocTestFailure.got

Фактический вывод примера.

exception doctest.UnexpectedException(test, example, exc_info)

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

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

UnexpectedException.test

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

UnexpectedException.example

Пример Example, который завершился ошибкой.

UnexpectedException.exc_info

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

Ящик для сообщений

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

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

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

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

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

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

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

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

if __name__ == '__main__':
    import doctest
    flags = doctest.REPORT_NDIFF|doctest.FAIL_FAST
    if len(sys.argv) > 1:
        name = sys.argv[1]
        if name in globals():
            obj = globals()[name]
        else:
            obj = __test__[name]
        doctest.run_docstring_examples(obj, globals(), name=name,
                                       optionflags=flags)
    else:
        fail, total = doctest.testmod(optionflags=flags)
        print(f"{fail} failures out of {total} tests")

Примечания

[1]

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

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/library/doctest.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API