doctest — Test interactive Python examples

Вихідний код: 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.
$

That’s all you need to know to start making productive use of doctest! Jump in. The following sections provide full details. Note that there are many examples of doctests in the standard Python test suite and libraries. Especially useful examples can be found in the standard test file Lib/test/test_doctest/test_doctest.py.

Просте використання: перевірка прикладів у Docstrings

The simplest way to start using doctest (but not necessarily the way you’ll continue to do it) is to end each module M with:

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

doctest then examines docstrings in module M.

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

python M.py

Це не відображатиме нічого, якщо приклад не буде невдалим, у цьому випадку невдалий(і) приклад(и) і причину(и) помилки(и) друкуються в stdout, а останній рядок виводу буде ***Test Помилка*** N невдач., де N — це кількість прикладів, що не вдалося виконати.

Натомість запустіть його з перемикачем -v:

python M.py -v

і детальний звіт про всі спробовані приклади друкується на стандартному виводі разом із різноманітними підсумками в кінці.

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

Існує також ярлик командного рядка для запуску testmod(). Ви можете наказати інтерпретатору Python запустити модуль doctest безпосередньо зі стандартної бібліотеки та передати ім’я модуля (імена) у командному рядку:

python -m doctest -v example.py

Це імпортує example.py як окремий модуль і запускає testmod() на ньому. Зауважте, що це може не працювати належним чином, якщо файл є частиною пакета та імпортує інші підмодулі з цього пакета.

Додаткову інформацію про testmod() див. у розділі Базовий API.

Просте використання: перевірка прикладів у текстовому файлі

Іншим простим застосуванням doctest є тестування інтерактивних прикладів у текстовому файлі. Це можна зробити за допомогою функції testfile():

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

Цей короткий сценарій виконує та перевіряє будь-які інтерактивні приклади Python, що містяться у файлі example.txt. Вміст файлу обробляється так, ніби це один гігантський рядок документа; файл не повинен містити програму Python! Наприклад, можливо, example.txt містить таке:

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

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

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

    >>> from example import factorial

Now use it:

    >>> factorial(6)
    120

Запуск doctest.testfile("example.txt") потім знаходить помилку в цій документації:

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

Як і у випадку з testmod(), testfile() нічого не відображатиме, якщо приклад не вдасться. Якщо приклад не вдається, то невдалий(і) приклад(и) і причина(и) невдачі(ів) друкуються у stdout, використовуючи той самий формат, що й testmod().

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

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

Існує також ярлик командного рядка для запуску testfile(). Ви можете наказати інтерпретатору Python запустити модуль doctest безпосередньо зі стандартної бібліотеки та передати імена файлів у командному рядку:

python -m doctest -v example.txt

Оскільки назва файлу не закінчується на .py, doctest робить висновок, що його потрібно запускати з testfile(), а не testmod().

Для отримання додаткової інформації про testfile() див. розділ Базовий API.

Як це працює

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

Які рядки документів перевіряються?

Здійснюється пошук у рядку документів модуля та всіх рядках документів функцій, класів і методів. Об’єкти, імпортовані в модуль, не шукаються.

In addition, there are cases when you want tests to be part of a module but not part of the help text, which requires that the tests not be included in the docstring. Doctest looks for a module-level variable called __test__ and uses it to locate other tests. If M.__test__ exists, it must be a dict, and each entry maps a (string) name to a function object, class object, or string. Function and class object docstrings found from M.__test__ are searched, and strings are treated as if they were docstrings. In output, a key K in M.__test__ appears with name M.__test__.K.

For example, place this block of code at the top of example.py:

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

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

The value of example.__test__["numbers"] will be treated as a docstring and all the tests inside it will be run. It is important to note that the value can be mapped to a function, class object, or module; if so, doctest searches them recursively for docstrings, which are then scanned for tests.

Будь-які знайдені класи рекурсивно шукаються подібним чином, щоб перевірити рядки документів у їх методах і вкладених класах.

Як розпізнаються приклади Docstring?

У більшості випадків копіювання та вставлення сеансу інтерактивної консолі працює добре, але doctest не намагається зробити точну емуляцію будь-якої конкретної оболонки Python.

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

Будь-який очікуваний вихід має слідувати одразу за останнім '>>> ' або '... ' рядком, що містить код, і очікуваний вихід (якщо такий є) поширюється на наступний '>>> " або рядок із пробілами.

Дрібний шрифт:

  • Очікуваний вихід не може містити рядок із пробілами, оскільки такий рядок використовується для сигналізації про кінець очікуваного виведення. Якщо очікуваний результат містить порожній рядок, додайте <BLANKLINE> у прикладі doctest у кожному місці, де очікується порожній рядок.

  • Усі жорсткі символи табуляції розгорнуті на пробіли за допомогою позицій табуляції з 8 стовпців. Вкладки у вихідних даних, згенерованих тестованим кодом, не змінюються. Оскільки будь-які жорсткі вкладки у вихідних даних зразка розгорнуті, це означає, що якщо вивід коду містить жорсткі вкладки, єдиний спосіб проходження doctest — це параметр NORMALIZE_WHITESPACE або директива в ефекті. Крім того, тест можна переписати, щоб зафіксувати результат і порівняти його з очікуваним значенням як частину тесту. Таку обробку вкладок у вихідному коді було досягнуто шляхом проб і помилок, і це виявилося найменш схильним до помилок способом обробки. Можна використати інший алгоритм для обробки вкладок, написавши спеціальний клас DocTestParser.

  • Вихідні дані в stdout фіксуються, але не виводяться в stderr (зворотні дані за винятками фіксуються іншим способом).

  • Якщо ви продовжуєте рядок через зворотну косу риску в інтерактивному сеансі або з будь-якої іншої причини використовуєте зворотну косу риску, вам слід використовувати необроблений рядок документації, який збереже ваші зворотні косі риски точно так, як ви їх вводите:

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

    В іншому випадку зворотна коса риска буде інтерпретуватися як частина рядка. Наприклад, \n вище буде інтерпретовано як символ нового рядка. Крім того, ви можете подвоїти кожну зворотну косу риску у версії doctest (і не використовувати необроблений рядок):

    >>> def f(x):
    ...     '''Backslashes in a raw docstring: m\\n'''
    >>> print(f.__doc__)
    Backslashes in a raw docstring: m\n
    
  • Початковий стовпець не має значення:

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

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

Що таке контекст виконання?

By default, each time doctest finds a docstring to test, it uses a shallow copy of M’s globals, so that running tests doesn’t change the module’s real globals, and so that one test in M can’t leave behind crumbs that accidentally allow another test to work. This means examples can freely use any names defined at top-level in M, and names defined earlier in the docstring being run. Examples cannot see names defined in other docstrings.

Ви можете примусово використовувати свій власний 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

Цей doctest завершується успішно, якщо виникає ValueError, а list.remove(x): x немає в деталях списку, як показано.

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

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

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

  • Коли вказано параметр doctest IGNORE_EXCEPTION_DETAIL, все, що йде після крайньої лівої двокрапки, і будь-яка інформація модуля в назві винятку ігнорується.

  • Інтерактивна оболонка пропускає рядок заголовка трасування для деяких SyntaxErrors. Але doctest використовує рядок заголовка трасування, щоб відрізнити винятки від невинятків. Тож у тих рідкісних випадках, коли вам потрібно перевірити SyntaxError, який пропускає заголовок трасування, вам потрібно буде вручну додати рядок заголовка трасування до вашого тестового прикладу.

  • For some exceptions, Python displays the position of the error using ^ markers and tildes:

    >>> 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 змінив тип повернення багатьох функцій з цілого на логічний; doctests, які очікують виводу «маленького цілого», все ще працюють у цих випадках. Цей варіант, ймовірно, зникне, але не через кілька років.

doctest.DONT_ACCEPT_BLANKLINE

За замовчуванням, якщо очікуваний блок виводу містить рядок, що містить лише рядок <BLANKLINE>, тоді цей рядок відповідатиме порожньому рядку у фактичному виведенні. Оскільки справді порожній рядок розмежовує очікуваний вихід, це єдиний спосіб повідомити, що очікується порожній рядок. Якщо вказано DONT_ACCEPT_BLANKLINE, ця заміна не дозволяється.

doctest.NORMALIZE_WHITESPACE

Якщо вказано, усі послідовності пробілів (пробілі та нові рядки) розглядаються як однакові. Будь-яка послідовність пробілів у очікуваному виводі відповідатиме будь-якій послідовності пробілів у фактичному виведенні. За замовчуванням пробіли мають точно збігатися. NORMALIZE_WHITESPACE особливо корисний, коли рядок очікуваного результату дуже довгий, і ви хочете обернути його між кількома рядками у своєму джерелі.

doctest.ELLIPSIS

Якщо вказано, маркер еліпса (...) в очікуваному виведенні може збігатися з будь-яким підрядком у фактичному виведенні. Це включає в себе підрядки, які охоплюють межі рядка, і порожні підрядки, тому найкраще використовувати це просто. Складне використання може призвести до тих самих типів «ой, це збігається занадто багато!» сюрпризів, до яких схильний .* у регулярних виразах.

doctest.IGNORE_EXCEPTION_DETAIL

When specified, doctests expecting exceptions pass so long as an exception of the expected type is raised, even if the details (message and fully qualified exception name) don’t match.

For example, an example expecting ValueError: 42 will pass if the actual exception raised is ValueError: 3*14, but will fail if, say, a TypeError is raised instead. It will also ignore any fully qualified name included before the exception class, which can vary between implementations and versions of Python and the code/libraries in use. Hence, all three of these variations will work with the flag specified:

>>> 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 повідомляти про правильні приклади, які вийшли з ладу через попередні помилки; але це також може приховати некоректні приклади, які завершуються невдачею незалежно від першої невдачі. Якщо вказано REPORT_ONLY_FIRST_FAILURE, інші приклади все ще виконуються та враховуються до загальної кількості повідомлених про помилки; пригнічується тільки вихід.

doctest.FAIL_FAST

Якщо вказано, вийти після першого невдалого прикладу та не намагатися запустити решту прикладів. Таким чином, кількість повідомлених помилок становитиме щонайбільше 1. Цей прапорець може бути корисним під час налагодження, оскільки приклади після першої помилки навіть не створюватимуть вихід налагодження.

Командний рядок doctest приймає опцію -f як скорочення -o FAIL_FAST.

Нове в версії 3.4.

doctest.REPORTING_FLAGS

Бітова маска або об’єднання всіх наведених вище позначок звітування.

Існує також спосіб зареєструвати нові назви прапорців параметрів, хоча це не корисно, якщо ви не маєте намір розширити внутрішні doctest через підкласи:

doctest.register_optionflag(name)

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

MY_FLAG = register_optionflag('MY_FLAG')

Директиви

Директиви Doctest можна використовувати для зміни прапорців параметрів для окремого прикладу. Директиви Doctest — це спеціальні коментарі Python, які слідують за вихідним кодом прикладу:

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

Пробіли не допускаються між «+» або «-» та назвою параметра директиви. Назва опції директиви може бути будь-якою з імен прапорців опції, пояснених вище.

Директиви doctest прикладу змінюють поведінку doctest для цього окремого прикладу. Використовуйте +, щоб увімкнути вказану поведінку, або -, щоб вимкнути її.

Наприклад, цей тест проходить:

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

Без директиви це було б невдалим, тому що фактичний вивід не має двох пробілів перед однозначними елементами списку, а також тому, що фактичний вивід містить один рядок. Цей тест також проходить, і для цього також потрібна директива:

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

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

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

Якщо для одного прикладу використовується кілька коментарів директиви, вони об’єднуються:

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

Як показано в попередньому прикладі, ви можете додати до свого прикладу рядки ..., які містять лише директиви. Це може бути корисно, коли приклад надто довгий, щоб директива могла зручно розміститися в одному рядку:

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

Зауважте, що оскільки всі параметри вимкнено за замовчуванням, а директиви застосовуються лише до прикладу, у якому вони з’являються, увімкнення параметрів (через + у директиві) зазвичай є єдиним значущим вибором. Однак позначки параметрів також можна передати функціям, які запускають doctests, встановлюючи різні значення за замовчуванням. У таких випадках може бути корисним вимкнення опції за допомогою - у директиві.

Попередження

doctest серйозно ставиться до вимог точних збігів в очікуваних результатах. Якщо хоча б один символ не збігається, тест проходить невдало. Можливо, це кілька разів вас здивує, оскільки ви дізнаєтесь, що саме робить Python і що не гарантує вихід. Наприклад, під час друку набору Python не гарантує, що елемент буде надруковано в будь-якому певному порядку, тому такий тест:

>>> foo()
{"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 безпечні на всіх платформах, і я часто створюю приклади doctest для отримання чисел такої форми:

>>> 3./4  # utterly safe
0.75

Прості дроби також легше зрозуміти людям, і це покращує документацію.

Базовий API

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

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)

Усі аргументи, окрім ім’я файлу, є необов’язковими та мають бути вказані у формі ключового слова.

Тестові приклади у файлі з назвою ім’я_файлу. Повернення (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, який буде використовуватися як глобальні під час виконання прикладів. Для doctest створюється нова поверхнева копія цього dict, тому її приклади починаються з чистого аркуша. За замовчуванням або якщо None, використовується новий порожній dict.

Необов’язковий аргумент extraglobs дає dict, об’єднаний із глобальними значеннями, які використовуються для виконання прикладів. Це працює як dict.update(): якщо globs і extraglobs мають спільний ключ, пов’язане значення в extraglobs з’являється в комбінованому dict. За замовчуванням або якщо None, додаткові глобальні значення не використовуються. Це розширена функція, яка дозволяє параметризувати doctests. Наприклад, doctest можна написати для базового класу, використовуючи загальну назву для класу, а потім повторно використовувати для тестування будь-якої кількості підкласів, передавши extraglobs dict, що відображає загальну назву на підклас, який потрібно перевірити.

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

Необов’язковий аргумент report друкує підсумок у кінці, якщо значення true, інакше нічого не друкує в кінці. У багатослівному режимі резюме є детальним, інакше резюме є дуже коротким (насправді порожнім, якщо всі тести пройдено).

Необов’язковий аргумент optionflags (значення за замовчуванням 0) приймає побітове АБО прапорів параметрів. Дивіться розділ Прапори параметрів.

Додатковий аргумент raise_on_error за умовчанням має значення false. Якщо істина, виняток виникає після першої помилки або несподіваного винятку в прикладі. Це дозволяє посмертно виправляти помилки. Типовою поведінкою є продовження виконання прикладів.

Необов’язковий аргумент parser визначає DocTestParser (або підклас), який слід використовувати для отримання тестів із файлів. За замовчуванням використовується звичайний аналізатор (тобто DocTestParser()).

Необов’язковий аргумент encoding визначає кодування, яке слід використовувати для перетворення файлу в Юнікод.

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

Усі аргументи є необов’язковими, і всі, крім m, мають бути вказані у формі ключового слова.

Тестові приклади в рядках документів у функціях і класах, доступних із модуля m (або модуля __main__, якщо m не вказано або має значення None), починаючи з m.__doc__.

Also test examples reachable from dict m.__test__, if it exists. m.__test__ maps names (strings) to functions, classes and strings; function and class docstrings are searched for examples; strings are searched directly, as if they were docstrings.

Пошук здійснюється лише в рядках документів, приєднаних до об’єктів, що належать модулю m.

Повернення (failure_count, test_count).

Необов’язковий аргумент name дає назву модуля; за замовчуванням або, якщо None, m.__name__ використовується.

Optional argument exclude_empty defaults to false. If true, objects for which no doctests are found are excluded from consideration. The default is a backward compatibility hack, so that code still using doctest.master.summarize in conjunction with testmod() continues to get output for objects with no tests. The exclude_empty argument to the newer DocTestFinder constructor defaults to 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 використовується для контексту виконання.

Необов’язковий аргумент ім’я використовується в повідомленнях про помилку та за умовчанням має значення "NoName".

Якщо додатковий аргумент verbose має значення true, вихідні дані генеруються, навіть якщо немає помилок. За замовчуванням вихідні дані генеруються лише у випадку помилки прикладу.

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

Необов’язковий аргумент optionflags працює як для функції testfile() вище.

Unittest API

As your collection of doctest’ed modules grows, you’ll want a way to run all their doctests systematically. doctest provides two functions that can be used to create unittest test suites from modules and text files containing doctests. To integrate with unittest test discovery, include a load_tests function in your test module:

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.

The returned unittest.TestSuite is to be run by the unittest framework and runs the interactive examples in each file. If an example in any file fails, then the synthesized unit test fails, and a failureException exception is raised showing the name of the file containing the test and a (sometimes approximate) line number.

Передайте один або кілька шляхів (у вигляді рядків) до текстових файлів, які потрібно перевірити.

Параметри можуть бути надані як аргументи ключових слів:

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

  • Якщо module_relative має значення True (за замовчуванням), тоді кожне ім’я файлу в paths визначає незалежний від ОС шлях щодо модуля. За замовчуванням цей шлях є відносно каталогу викликаючого модуля; але якщо вказано аргумент package, то він відноситься до цього пакета. Щоб забезпечити незалежність від ОС, кожне ім’я файлу має використовувати символи / для розділення сегментів шляху та не може бути абсолютним шляхом (тобто воно не може починатися з /).

  • Якщо module_relative має значення False, тоді кожне ім’я файлу в paths вказує шлях до ОС. Шлях може бути абсолютним або відносним; відносні шляхи вирішуються відносно поточного робочого каталогу.

Необов’язковий аргумент package — це пакет Python або ім’я пакета Python, чий каталог слід використовувати як базовий каталог для імен файлів, пов’язаних із модулями, у paths. Якщо пакет не вказано, то каталог модуля, що викликає, використовується як базовий каталог для імен файлів, що відносяться до модуля. Це помилка вказувати package, якщо module_relative має значення False.

Додатковий аргумент setUp визначає функцію налаштування для набору тестів. Це викликається перед виконанням тестів у кожному файлі. Функції setUp буде передано об’єкт DocTest. Функція setUp може отримати доступ до глобальних параметрів тесту як атрибута globs пройденого тесту.

Необов’язковий аргумент tearDown визначає функцію розриву для набору тестів. Це викликається після виконання тестів у кожному файлі. Функції tearDown буде передано об’єкт DocTest. Функція setUp може отримати доступ до глобальних параметрів тесту як атрибута globs пройденого тесту.

Додатковий аргумент globs — це словник, що містить початкові глобальні змінні для тестів. Для кожного тесту створюється нова копія цього словника. За замовчуванням globs — це новий порожній словник.

Необов’язковий аргумент optionflags вказує параметри тесту за замовчуванням для тестів, створені шляхом об’єднання окремих прапорців параметрів. Дивіться розділ Прапори параметрів. Перегляньте функцію set_unittest_reportflags() нижче, щоб дізнатися про кращий спосіб налаштування параметрів звітування.

Необов’язковий аргумент parser визначає DocTestParser (або підклас), який слід використовувати для отримання тестів із файлів. За замовчуванням використовується звичайний аналізатор (тобто DocTestParser()).

Необов’язковий аргумент encoding визначає кодування, яке слід використовувати для перетворення файлу в Юнікод.

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

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

Перетворіть тести doctest для модуля на unittest.TestSuite.

The returned unittest.TestSuite is to be run by the unittest framework and runs each doctest in the module. If any of the doctests fail, then the synthesized unit test fails, and a failureException exception is raised showing the name of the file containing the test and a (sometimes approximate) line number.

Необов’язковий аргумент module надає модуль для тестування. Це може бути об’єкт модуля або ім’я модуля (можливо з крапками). Якщо не вказано, використовується модуль, який викликає цю функцію.

Додатковий аргумент globs — це словник, що містить початкові глобальні змінні для тестів. Для кожного тесту створюється нова копія цього словника. За замовчуванням globs — це новий порожній словник.

Необов’язковий аргумент extraglobs визначає додатковий набір глобальних змінних, який об’єднується в globs. За замовчуванням додаткові глобальні значення не використовуються.

Необов’язковий аргумент test_finder — це об’єкт DocTestFinder (або замінник), який використовується для вилучення тестів документів із модуля.

Необов’язкові аргументи setUp, tearDown і optionflags такі самі, як і для функції DocFileSuite() вище.

Ця функція використовує ту саму техніку пошуку, що й testmod().

Змінено в версії 3.5: DocTestSuite() повертає порожній unittest.TestSuite, якщо module не містить рядків документів, замість того, щоб викликати ValueError.

exception doctest.failureException

When doctests which have been converted to unit tests by DocFileSuite() or DocTestSuite() fail, this exception is raised showing the name of the file containing the test and a (sometimes approximate) line number.

Under the covers, DocTestSuite() creates a unittest.TestSuite out of doctest.DocTestCase instances, and DocTestCase is a subclass of unittest.TestCase. DocTestCase isn’t documented here (it’s an internal detail), but studying its code can answer questions about the exact details of unittest integration.

Similarly, DocFileSuite() creates a unittest.TestSuite out of doctest.DocFileCase instances, and DocFileCase is a subclass of DocTestCase.

So both ways of creating a unittest.TestSuite run instances of DocTestCase. This is important for a subtle reason: when you run doctest functions yourself, you can control the doctest options in use directly, by passing option flags to doctest functions. However, if you’re writing a unittest framework, unittest ultimately controls when and how tests get run. The framework author typically wants to control doctest reporting options (perhaps, e.g., specified by command line options), but there’s no way to pass options through unittest to doctest test runners.

З цієї причини doctest також підтримує поняття прапорів звітності doctest, специфічних для підтримки unittest, через цю функцію:

doctest.set_unittest_reportflags(flags)

Встановіть прапорці звітів doctest для використання.

Аргумент flags приймає побітове АБО прапорів параметрів. Дивіться розділ Прапори параметрів. Можна використовувати лише «прапори звітності».

This is a module-global setting, and affects all future doctests run by module unittest: the runTest() method of DocTestCase looks at the option flags specified for the test case when the DocTestCase instance was constructed. If no reporting flags were specified (which is the typical and expected case), doctest’s unittest reporting flags are bitwise ORed into the option flags, and the option flags so augmented are passed to the DocTestRunner instance created to run the doctest. If any reporting flags were specified when the DocTestCase instance was constructed, doctest’s unittest reporting flags are ignored.

Функція повертає значення прапорів звітів unittest, які діяли до виклику функції.

Розширений API

Основний API — це проста оболонка, призначена для полегшення використання doctest. Він досить гнучкий і повинен відповідати потребам більшості користувачів; однак, якщо вам потрібен більш детальний контроль над тестуванням або ви бажаєте розширити можливості doctest, тоді вам слід скористатися розширеним API.

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

  • Example: один statement 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, якщо рядок недоступний, або якщо тест не було вилучено з рядка.

Приклад об’єктів

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

A dictionary mapping from option flags to True or False, which is used to override default options for this example. Any option flags not contained in this dictionary are left at their default value (as specified by the DocTestRunner’s optionflags). By default, no options are set.

Об’єкти DocTestFinder

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

Клас обробки, який використовується для вилучення DocTests, які мають відношення до даного об’єкта, з його рядка документації та рядків документації об’єктів, які містяться в ньому. DocTests можна отримати з модулів, класів, функцій, методів, статичних методів, методів класів і властивостей.

Необов’язковий аргумент verbose можна використовувати для відображення об’єктів, які шукав шукач. За замовчуванням значення False (без виведення).

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

Якщо необов’язковий аргумент 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 створюється нова копія глобального словника. Якщо 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. Необов’язковий аргумент ім’я – це ім’я, що ідентифікує цей рядок і використовується лише для повідомлень про помилки.

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

Розділіть заданий рядок на приклади та проміжний текст і поверніть їх як список чергування Exampleі рядків. Номери рядків для Examples засновані на 0. Необов’язковий аргумент ім’я – це ім’я, що ідентифікує цей рядок і використовується лише для повідомлень про помилки.

Об’єкти DocTestRunner

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

Клас обробки, який використовується для виконання та перевірки інтерактивних прикладів у DocTest.

Порівняння очікуваних і фактичних результатів виконується за допомогою OutputChecker. Це порівняння можна налаштувати за допомогою кількох позначок параметрів; дивіться розділ Прапори параметрів для отримання додаткової інформації. Якщо прапорців параметрів недостатньо, порівняння також можна налаштувати, передавши підклас OutputChecker до конструктора.

The test runner’s display output can be controlled in two ways. First, an output function can be passed to TestRunner.run(); this function will be called with strings that should be displayed. It defaults to sys.stdout.write. If capturing the output is not sufficient, then the display output can be also customized by subclassing DocTestRunner, and overriding the methods report_start(), report_success(), report_unexpected_exception(), and report_failure().

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

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

Необов’язковий аргумент ключового слова optionflags можна використовувати для керування тим, як програма виконання тестів порівнює очікуваний результат із фактичним результатом і як він відображає помилки. Для отримання додаткової інформації див. розділ Прапори параметрів.

DocTestRunner defines the following methods:

report_start(out, test, example)

Повідомте, що тестувальник збирається обробити наведений приклад. Цей метод надається, щоб дозволити підкласам DocTestRunner налаштовувати свій вихід; його не слід називати безпосередньо.

приклад — це приклад, який буде оброблено. тест — це тест, який містить приклад. out — це функція виводу, яка була передана в DocTestRunner.run().

report_success(out, test, example, got)

Повідомте, що наведений приклад виконано успішно. Цей метод надається, щоб дозволити підкласам DocTestRunner налаштовувати свій вихід; його не слід називати безпосередньо.

приклад — це приклад, який буде оброблено. got — фактичний вихід із прикладу. тест — це тест, що містить приклад. out — це функція виводу, яка була передана в DocTestRunner.run().

report_failure(out, test, example, got)

Повідомте, що наведений приклад не вдався. Цей метод надається, щоб дозволити підкласам DocTestRunner налаштовувати свій вихід; його не слід називати безпосередньо.

приклад — це приклад, який буде оброблено. got — фактичний вихід із прикладу. тест — це тест, що містить приклад. out — це функція виводу, яка була передана в DocTestRunner.run().

report_unexpected_exception(out, test, example, exc_info)

Повідомте, що наведений приклад викликав несподіваний виняток. Цей метод надається, щоб дозволити підкласам DocTestRunner налаштовувати свій вихід; його не слід називати безпосередньо.

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

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

Run the examples in test (a DocTest object), and display the results using the writer function out.

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

compileflags надає набір прапорів, які має використовувати компілятор Python під час виконання прикладів. Якщо не вказано, за замовчуванням використовуватиметься набір прапорів майбутнього імпорту, які застосовуються до globs.

The output of each example is checked using the DocTestRunner’s output checker, and the results are formatted by the DocTestRunner.report_*() methods.

summarize(verbose=None)

Print a summary of all the test cases that have been run by this DocTestRunner, and return a named tuple 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:

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

  • Клас DebugRunner є підкласом DocTestRunner, який створює виняток для першого невдалого прикладу, що містить інформацію про цей приклад. Ця інформація може бути використана для виконання посмертного налагодження на прикладі.

  • Випадки unittest, згенеровані DocTestSuite(), підтримують метод debug(), визначений unittest.TestCase.

  • Ви можете додати виклик до pdb.set_trace() у прикладі doctest, і ви перейдете до налагоджувача Python, коли цей рядок буде виконано. Потім ви можете перевірити поточні значення змінних і так далі. Наприклад, припустимо, що a.py містить лише цей модуль docstring:

    """
    >>> 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 — це рядок, що містить приклади 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 для об’єкта на сценарій.

Argument module is a module object, or dotted name of a module, containing the object whose doctests are of interest. Argument name is the name (within the module) of the object with the doctests of interest. The result is a string, containing the object’s docstring converted to a Python script, as described for script_from_examples() above. For example, if module a.py contains a top-level function f(), then

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

prints a script version of function f()“s docstring, with doctests converted to code, and the rest placed in comments.

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() вище, за винятком того, що рядок, що містить приклади doctest, вказується безпосередньо через аргумент 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 не збігається з його очікуваним результатом. Аргументи конструктора використовуються для ініціалізації однойменних атрибутів.

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__ із тем регресійних тестів на рядки документів, що містять тестові приклади.

Якщо ви розмістили свої тести в модулі, модуль сам може бути виконавцем тестів. Коли тест виходить невдалим, ви можете організувати повторний запуск лише невдалого документу, поки ви вирішуєте проблему. Ось мінімальний приклад такого тесту:

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

Виноски