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

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

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

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

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

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

python M.py

Это ничего не покажет, если только в примере не произойдет сбой, и в этом случае неудачные примеры и причины сбоя выводятся в стандартный вывод, а конечная строка вывода равна ***Test Failed*** N failures., где N - количество примеров это не удалось.

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

python M.py -v

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

Вы можете принудительно включить подробный режим, передав verbose=True в testmod(), или запретить его, передав verbose=False. В любом из этих случаев sys.argv не проверяется testmod() (поэтому передача -v или нет не имеет никакого эффекта).

Существует также ярлык командной строки для запуска testmod(). Вы можете поручить интерпретатору Python запустить модуль doctest непосредственно из стандартной библиотеки и передать имена модулей в командной строке:

python -m doctest -v example.py

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

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

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

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

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

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

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

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

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

    >>> from example import factorial

Now use it:

    >>> factorial(6)
    120

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

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

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

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

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

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

python -m doctest -v example.txt

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

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

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

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

Какие Строки Документации Проверяются

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

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

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

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

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

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

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

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

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

  • Все твердые символы табуляции заменяются пробелами с использованием 8-столбцовых разделителей табуляции. Табуляции в выходных данных, сгенерированных тестируемым кодом, не изменяются. Поскольку все жесткие табуляции в образце вывода расширены, это означает, что если вывод кода включает жесткие табуляции, то единственный способ, которым может быть выполнен тест, - это если используется параметр NORMALIZE_WHITESPACE или directive. В качестве альтернативы, тест можно переписать, чтобы получить выходные данные и сравнить их с ожидаемым значением в рамках теста. Такая обработка вкладок в исходном коде была разработана методом проб и ошибок и оказалась наименее подверженной ошибкам. Можно использовать другой алгоритм для обработки вкладок, написав пользовательский класс DocTestParser.

  • Вывод в стандартный вывод записывается, но не выводится в stderr (обратные трассировки исключений записываются другим способом).

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

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

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

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

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

А Как насчет Исключений

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

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

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

Этот тест завершается успешно, если задано значение ValueError с указанием list.remove(x): x not in list, как показано на рисунке.

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

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

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

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

  • Doctest не может угадать, были ли ваши ожидаемые результаты получены в результате обратной трассировки исключения или в результате обычной печати. Так, например, пример, который ожидает, что ValueError: 42 is prime будет передан независимо от того, действительно ли ValueError поднят или если в примере просто выводится этот текст обратной трассировки. На практике обычный вывод редко начинается со строки заголовка обратной трассировки, так что реальных проблем это не создает.

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

  • Если указан параметр IGNORE_EXCEPTION_DETAIL doctest, все, что следует за крайним левым двоеточием, и любая информация о модуле в имени исключения игнорируются.

  • Интерактивная оболочка пропускает строку заголовка traceback для некоторых SyntaxErrors. Но doctest использует строку заголовка traceback, чтобы отличать исключения от неисключений. Таким образом, в том редком случае, когда вам нужно протестировать 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'
    

Флажки опций

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

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

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

doctest.DONT_ACCEPT_TRUE_FOR_1

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

doctest.DONT_ACCEPT_BLANKLINE

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

doctest.NORMALIZE_WHITESPACE

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

doctest.ELLIPSIS

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

doctest.IGNORE_EXCEPTION_DETAIL

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

Например, пример, ожидающий ValueError: 42, пройдет, если фактическое вызванное исключение равно ValueError: 3*14, но завершится ошибкой, если, скажем, вместо этого будет вызвано TypeError. Он также будет игнорировать любое полное имя, указанное перед классом exception, которое может варьироваться в зависимости от реализаций и версий 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 служат как документацией, так и тестовыми примерами, и пример должен быть включен в целях документирования, но не должен проверяться. Например, выходные данные примера могут быть случайными; или пример может зависеть от ресурсов, которые были бы недоступны тестировщику.

Флаг ПРОПУСКА также можно использовать для временного «комментирования» примеров.

doctest.COMPARISON_FLAGS

Битовую маску или объедините все приведенные выше флаги сравнения.

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

doctest.REPORT_UDIFF

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

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 могут использоваться для изменения option flags в отдельном примере. Директивы Doctest - это специальные комментарии Python, следующие за примером исходного кода.:

directive             ::=  "#" "doctest:" directive_options
directive_options     ::=  directive_option ("," directive_option)*
directive_option      ::=  on_or_off directive_option_name
on_or_off             ::=  "+" | "-"
directive_option_name ::=  "DONT_ACCEPT_BLANKLINE" | "NORMALIZE_WHITESPACE" | ...

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

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

Например, этот тест проходит успешно:

>>> print(list(range(20)))  # doctest: +NORMALIZE_WHITESPACE
[0,   1,  2,  3,  4,  5,  6,  7,  8,  9,
10,  11, 12, 13, 14, 15, 16, 17, 18, 19]

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

>>> print(list(range(20)))  # doctest: +ELLIPSIS
[0, 1, ..., 18, 19]

В одной физической строке можно использовать несколько директив, разделенных запятыми:

>>> print(list(range(20)))  # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
[0,    1, ...,   18,    19]

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

>>> print(list(range(20)))  # doctest: +ELLIPSIS
...                         # doctest: +NORMALIZE_WHITESPACE
[0,    1, ...,   18,    19]

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

>>> print(list(range(5)) + list(range(10, 20)) + list(range(30, 40)))
... # doctest: +ELLIPSIS
[0, ..., 4, 10, ..., 19, 30, ..., 39]

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

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

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

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

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

>>> 3./4  # utterly safe
0.75

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

Базовый API

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

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

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

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

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

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

  • Если значение module_relative равно False, то filename указывает путь, зависящий от операционной системы. Путь может быть абсолютным или относительным; относительные пути разрешаются по отношению к текущему рабочему каталогу.

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

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

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

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

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

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

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

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

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

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

Верните (failure_count, test_count).

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

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

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

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

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

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

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

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

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

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

Unittest API -интерфейс Unittest API

По мере роста вашей коллекции проверенных модулей вам понадобится способ систематического выполнения всех их тестов. doctest предоставляет две функции, которые можно использовать для создания unittest тестовых наборов из модулей и текстовых файлов, содержащих тесты. Чтобы интегрироваться с unittest test discovery, включите функцию 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 из текстовых файлов и модулей с тестами doctests:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

doctest.DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, setUp=None, tearDown=None, checker=None)

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

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

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

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

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

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

Необязательные аргументы 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 принимает значение bitwise OR из флагов параметров. Смотрите раздел Флажки опций. Можно использовать только «флаги отчетов».

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

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

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

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

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

  • Example: один Python statement, связанный с ожидаемым результатом.

  • DocTest: набор Example, обычно извлекаемый из одной строки документации или текстового файла.

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

  • DocTestFinder: Находит все строки документации в данном модуле и использует DocTestParser для создания DocTest из каждой строки документации, содержащей интерактивные примеры.

  • DocTestParser: Создает объект DocTest из строки (например, строки документации объекта).

  • DocTestRunner: Выполняет примеры в виде DocTest и использует OutputChecker для проверки их вывода.

  • OutputChecker: Сравнивает фактический результат из примера doctest с ожидаемым результатом и решает, совпадают ли они.

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

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

Проверенные объекты

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

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

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

examples

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

globs

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

name

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

filename

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

lineno

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

docstring

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

Примеры объектов

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

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

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

source

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

want

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

exc_msg

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

lineno

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

indent

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

options

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

Объекты DocTestFinder

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

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

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

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

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

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

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

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

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

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

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

  • В качестве пространства имен по умолчанию, если не указано значение globs.

  • Чтобы DocTestFinder не извлекал DocTests из объектов, импортированных из других модулей. (Объекты, содержащие модули, отличные от module, игнорируются.)

  • Чтобы найти имя файла, содержащего объект.

  • Чтобы помочь найти номер строки объекта в его файле.

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

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

Объекты DocTestParser

class doctest.DocTestParser

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

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

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

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

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

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

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

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

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

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

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

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

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

report_start(out, test, example)

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

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

report_success(out, test, example, got)

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

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

report_failure(out, test, example, got)

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

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

report_unexpected_exception(out, test, example, exc_info)

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

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

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

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

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

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

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

summarize(verbose=None)

Выведите сводную информацию обо всех тестовых примерах, которые были запущены этим DocTestRunner, и верните named tuple TestResults(failed, attempted).

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

Объекты средства проверки вывода

class doctest.OutputChecker

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

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

check_output(want, got, optionflags)

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

output_difference(example, got, optionflags)

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

Отладка

Doctest предоставляет несколько механизмов для отладки примеров doctest:

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

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

  • Обращения unittest, сгенерированные с помощью DocTestSuite(), поддерживают метод debug(), определенный с помощью unittest.TestCase.

  • Вы можете добавить вызов 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)
    >>>
    

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

doctest.script_from_examples(s)

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

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

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

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

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

Отладьте тесты документации для объекта.

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

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

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

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

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

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

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

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

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

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

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

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

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

exception doctest.DocTestFailure(test, example, got)

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

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

DocTestFailure.test

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

DocTestFailure.example

Ошибка Example, которая не удалась.

DocTestFailure.got

Примеры фактических результатов.

exception doctest.UnexpectedException(test, example, exc_info)

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

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

UnexpectedException.test

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

UnexpectedException.example

Ошибка Example, которая не удалась.

UnexpectedException.exc_info

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

Коробка для мыла

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

  1. Проверка примеров в строках документации.

  2. Регрессионное тестирование.

  3. Грамотное тестирование исполняемой документации.

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

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

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

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

  • Напишите текстовые файлы, содержащие тестовые примеры в качестве интерактивных примеров, и протестируйте файлы, используя 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))

Сноски

Вернуться на верх