Почему импорт модуля нарушает мой doctest (Python 2.7)

Я попытался использовать экземпляр StringIO в doctest в моем классе в программе Python 2.7. Вместо того, чтобы получить какие-либо результаты теста, я получаю ответ «Ничего не получил».

Этот упрощенный тестовый пример демонстрирует ошибку:

#!/usr/bin/env python2.7
# encoding: utf-8

class Dummy(object):
    '''Dummy: demonstrates a doctest problem

    >>> from StringIO import StringIO
    ... s = StringIO()
    ... print("s is created")
    s is created
    '''

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

Ожидаемое поведение: тест пройден.

Наблюдаемое поведение: тест не пройден, вывод такой:

% ./src/doctest_fail.py
**********************************************************************
File "./src/doctest_fail.py", line 7, in __main__.Dummy
Failed example:
    from StringIO import StringIO
    s = StringIO()
    print("s is created")
Expected:
    s is created
Got nothing
**********************************************************************
1 items had failures:
   1 of   1 in __main__.Dummy
***Test Failed*** 1 failures.

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


person Jim DeLaHunt    schedule 09.12.2016    source источник
comment
Что с ... вместо >>> в нескольких строках без продолжения?   -  person user2357112 supports Monica    schedule 10.12.2016
comment
Как ясно из ответов, проблема действительно в синтаксисе doctest, а не в StringIO. Я удаляю тег StringIO и переформулирую вопрос, чтобы не упоминать StringIO.   -  person Jim DeLaHunt    schedule 11.12.2016


Ответы (2)


Синтаксис строки продолжения (...) сбивает с толку парсер doctest. Это работает:

#!/usr/bin/env python2.7
# encoding: utf-8

class Dummy(object):
    '''Dummy: demonstrates a doctest problem

    >>> from StringIO import StringIO
    >>> s = StringIO()
    >>> print("s is created")
    s is created
    '''

if __name__ == "__main__":
    import doctest
    doctest.testmod()
person wim    schedule 09.12.2016
comment
Спасибо! Вы правы, мое использование синтаксиса строки продолжения было проблемой, и переход на синтаксис >>> исправляет это. Вы не делаете следующий шаг, чтобы объяснить, почему мой синтаксис был неверен с точки зрения семантики doctest. Я напишу свой собственный ответ, чтобы сделать это. - person Jim DeLaHunt; 11.12.2016

[Основываясь на правильном ответе wim, но объясняя, почему немного больше, с учетом базовой семантики doctest.]

Пример не работает, потому что он использует синтаксис PS2 (... ) вместо синтаксиса PS1 (>>>) перед отдельными простые высказывания.

Измените ... на >>>:

#!/usr/bin/env python2.7
# encoding: utf-8

class Dummy(object):
    '''Dummy: demonstrates a doctest problem

    >>> from StringIO import StringIO
    >>> s = StringIO()
    >>> print("s is created")
    s is created
    '''

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

Теперь исправленный пример, переименованный в doctest_pass.py, работает без ошибок. Он не выводит никаких результатов, что означает, что все тесты проходят:

% src/doctest_pass.py                       

Почему синтаксис >>> правильный? Справочник по библиотеке Python для doctest, 25.2.3.2. Как распознаются примеры строк документации? должно быть место, где можно найти ответ, но этот синтаксис не очень ясен.

Doctest сканирует строку документации в поисках «Примеров». Там, где он видит строку PS1 >>>, он берет все оттуда до конца строки в качестве примера. Он также добавляет в пример все следующие строки, начинающиеся со строки PS2 ... (см.: _EXAMPLE_RE в классе doctest.DocTestParser, строки 584-595). Он принимает последующие строки до следующей пустой строки или строки, начинающейся со строки PS1, в качестве желаемого вывода.

Doctest компилирует каждый пример как интерактивный оператор Python, используя compile() встроенная функция в execоператоре ( См.: doctest.DocTestRunner.__run(), строки 1314-1315).

"интерактивный оператор" — это список операторов, заканчивающийся новой строки или составной оператор. Составное утверждение, например. оператор if или try, «как правило, [… занимает] несколько строк, хотя в простых воплощениях весь составной оператор может содержаться в одной строке». Вот многострочный составной оператор:

if 1 > 0:
    print("As expected")
else:
    print("Should not happen")

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

from StringIO import StringIO
s = StringIO(); print("s is created")

Итак, doctest вопроса не прошел, потому что он содержал один пример с тремя простыми утверждениями и без разделителей с запятой. Замена строк PS2 на строки PS1 прошла успешно, поскольку строка документации превращается в последовательность из трех примеров, каждый из которых содержит одно простое выражение. Хотя эти три строки работают вместе, чтобы настроить один тест одной части функциональности, они не являются единым тестовым приспособлением. Это три теста, два из которых настраивают состояние, но не проверяют основную функциональность.

Кстати, вы можете увидеть количество примеров, которые doctest распознает, используя флаг -v. Обратите внимание, что там написано "3 tests in __main__.Dummy". Можно подумать о трех строках как об одной тестовой единице, но doctest видит три примера. Первые два примера не имеют ожидаемого результата. Когда пример выполняется и не генерирует выходных данных, это считается «проходом».

% src/doctest_pass.py -v
Trying:
    from StringIO import StringIO
Expecting nothing
ok
Trying:
    s = StringIO()
Expecting nothing
ok
Trying:
    print("s is created")
Expecting:
    s is created
ok
1 items had no tests:
    __main__
1 items passed all tests:
   3 tests in __main__.Dummy
3 tests in 2 items.
3 passed and 0 failed.
Test passed.

В одной строке документации примеры выполняются последовательно. Изменения состояния из каждого примера сохраняются для следующих примеров в той же строке документации. Таким образом, оператор import определяет имя модуля, оператор присваивания s = использует это имя модуля и определяет имя переменной и так далее. Документация по doctest, 25.2.3.3 . Что такое контекст выполнения?, косвенно раскрывает это, когда говорит: «Примеры могут свободно использовать… имена, определенные ранее в выполняемой строке документации».

Предыдущее предложение в этом разделе: «Каждый раз, когда doctest находит строку документации для тестирования, он использует неглубокую копию глобальных переменных M, так что… один тест в M не может оставить после себя крошки, которые случайно позволят другому тесту работать», — это немного вводит в заблуждение. Это правда, что один тест в M не может повлиять на тест в другой строке документации. Однако в пределах одной строки документации более ранний тест обязательно оставит после себя крохи, которые вполне могут повлиять на более поздние тесты.

Почему пример в справочнике по библиотеке Python для doctest, 25.2.3.2. Как распознаются примеры строк документа?, показать пример с синтаксисом ...? В этом примере показан оператор if, составной оператор, состоящий из нескольких строк. Вторая и последующие строки отмечены строками PS2.

person Jim DeLaHunt    schedule 11.12.2016