Spec-Zone.ru › Python 3.12

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

Исходный код: Lib/doctest.py

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

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

Вот пример модуля:

"""
This is the "example" module.

The example module supplies one function, factorial().  For example,

>>> factorial(5)
120
"""

def factorial(n):
    """Return the factorial of n, an exact integer >= 0.

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

    Factorials of floats are OK, but the float must be an exact integer:
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
    >>> factorial(30.0)
    265252859812191058636308480000000

    It must also not be ridiculously large:
    >>> factorial(1e100)
    Traceback (most recent call last):
        ...
    OverflowError: n too large
    """

    import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result


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

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

$ python example.py
$

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

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

И так далее, в конечном итоге заканчиваясь:

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

Это всё, что вам нужно знать, чтобы начать продуктивно использовать doctest! Приступайте. В следующих разделах приводятся подробные сведения. Обратите внимание, что в стандартном наборе тестов и библиотеках Python есть много примеров doctest. Особенно полезные примеры можно найти в стандартном тестовом файле 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=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() не отобразит ничего, пока пример не завершится неудачно. Если пример завершится неудачно, то неудачный пример(ы) и причина(ы) неудачи(ей) выводятся в 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 на этих примерах см. в следующих разделах.

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

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

Кроме того, бывают случаи, когда вы хотите, чтобы тесты были частью модуля, но не частью справки, что требует исключения тестов из строки документации. 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. Эллипсис в этом примере может быть опущен или может быть заменен на три (или три сотни) запятых, цифр или отступом сценария Monty Python.

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

  • 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. Символьные имена флагов предоставляются в качестве констант модуля, которые могут быть побитово объединены и переданы в различные функции. Эти имена также могут использоваться в директивах 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 выводит много информации, если true, и выводит только провалы, если false; по умолчанию или если None, он равен true тогда и только тогда, когда '-v' находится в sys.argv.

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

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

Необязательный аргумент 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__.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Объекты DocTest

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

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

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

examples

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

globs

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

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

exc_msg

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

lineno

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

indent

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

options

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

Объекты DocTestFinder

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

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

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

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

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

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

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.

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

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

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

summarize(verbose=None)

Вывести сводку всех тестов, выполненных этим DocTestRunner, и вернуть кортеж TestResults(failed, attempted).

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

Объекты OutputChecker

class doctest.OutputChecker

Класс для проверки, соответствует ли фактический вывод примера doctest ожидаемому выводу. OutputChecker определяет два метода: check_output(), который сравнивает пару выводов и возвращает True если они совпадают; и output_difference(), который возвращает строку, описывающую различия между двумя выводами.

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

check_output(want, got, optionflags)

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

output_difference(example, got, optionflags)

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

END_OF_DOCUMENT_MARKER

Отладка

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

exception doctest.DocTestFailure(test, example, got)

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

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

DocTestFailure.test

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

DocTestFailure.example

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

DocTestFailure.got

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

exception doctest.UnexpectedException(test, example, exc_info)

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

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

UnexpectedException.test

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

UnexpectedException.example

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

UnexpectedException.exc_info

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

END_OF_DOCUMENT_MARKER

Soapbox

Как упоминалось во введении, 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("{} failures out of {} tests".format(fail, total))

Примечания

[1]

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

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

Spec-Zone.ru

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