Spec-Zone.ru › Python 3.11

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. Многоточие в этом примере можно убрать или заменить тремя (или тремястами) запятыми или цифрами либо отступом с фрагментом скетча «Монти Пайтона».

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

  • 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

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

В командной строке 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]

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

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

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

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

может завершиться ошибкой! Один из способов обойти это — сделать так:

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

Другой способ — сделать так:

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

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

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

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

Директива ELLIPSIS позволяет красиво решить проблему в последнем примере:

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

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

>>> 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 при значении False аргумента module_relative приводит к ошибке.

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

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

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

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

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

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

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

API unittest

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

Необязательный аргумент 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, 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 statement вместе с ожидаемым результатом.
  • DocTest: Набор объектов Example, обычно извлечённых из одной строки документации или текстового файла.

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

  • DocTestFinder: Находит все строки документации в заданном модуле и использует DocTestParser для создания объекта DocTest для каждой строки документации, содержащей интерактивные примеры.
  • DocTestParser: Создаёт объект DocTest из строки (например, строки документации объекта).
  • 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 optionflags). По умолчанию параметры не заданы.

Объекты 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 не задан.
  • Чтобы DocTestFinder не извлекал 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 — это имя, идентифицирующее данную строку; оно используется только в сообщениях об ошибках.

Объекты DocTestRunner

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

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

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

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

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

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

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

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.

Отладка

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 не указан или имеет ложное значение, скрипт с самого начала запускается под отладчиком: в pdb.run() передаётся соответствующий вызов exec().

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

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

  • Создайте текстовые файлы с тестовыми случаями в виде интерактивных примеров и проверяйте их с помощью 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.11/library/doctest.html

Spec-Zone.ru

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