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

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

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

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

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

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

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

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

  • 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]

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

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

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

Для поиска, разбора, выполнения и проверки примеров 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.

Отображаемый вывод средства запуска тестов можно настроить двумя способами. Во-первых, функции вывода можно передать в 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 должен использовать при выполнении примеров. Если значение не указано, по умолчанию используется набор флагов будущих импортов, применимых к 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.
  • В пример doctest можно добавить вызов pdb.set_trace(), и при выполнении этой строки откроется отладчик Python. После этого можно проверять текущие значения переменных и так далее. Например, предположим, что a.py содержит только такую строку документации модуля:

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

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

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

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

doctest.script_from_examples(s)

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

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

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

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

выводит:

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

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

doctest.testsource(module, name)

Преобразовать doctest объекта в скрипт.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

exception doctest.DocTestFailure(test, example, got)

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

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

DocTestFailure.test

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

DocTestFailure.example

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

DocTestFailure.got

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

exception doctest.UnexpectedException(test, example, exc_info)

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

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

UnexpectedException.test

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

UnexpectedException.example

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

UnexpectedException.exc_info

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

Несколько замечаний

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

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

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

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

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

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

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

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

if __name__ == '__main__':
    import doctest
    flags = doctest.REPORT_NDIFF|doctest.FAIL_FAST
    if len(sys.argv) > 1:
        name = sys.argv[1]
        if name in globals():
            obj = globals()[name]
        else:
            obj = __test__[name]
        doctest.run_docstring_examples(obj, globals(), name=name,
                                       optionflags=flags)
    else:
        fail, total = doctest.testmod(optionflags=flags)
        print("{} 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