Тестирование предупреждений с помощью doctest - PullRequest
9 голосов
/ 10 марта 2010

Я хотел бы использовать doctests , чтобы проверить наличие определенных предупреждений. Например, предположим, у меня есть следующий модуль:

from warnings import warn

class Foo(object):
    """
    Instantiating Foo always gives a warning:

    >>> foo = Foo()
    testdocs.py:14: UserWarning: Boo!
      warn("Boo!", UserWarning)
    >>> 
    """

    def __init__(self):
        warn("Boo!", UserWarning)

Если я запускаю python -m doctest testdocs.py, чтобы запустить тестирование в своем классе и убедиться, что предупреждение напечатано, я получу:

testdocs.py:14: UserWarning: Boo!
  warn("Boo!", UserWarning)
**********************************************************************
File "testdocs.py", line 7, in testdocs.Foo
Failed example:
    foo = Foo()
Expected:
    testdocs.py:14: UserWarning: Boo!
      warn("Boo!", UserWarning)
Got nothing
**********************************************************************
1 items had failures:
   1 of   1 in testdocs.Foo
***Test Failed*** 1 failures.

Похоже, что предупреждение печатается, но не фиксируется и не замечается doctest. Я предполагаю, что это потому, что предупреждения выводятся на sys.stderr вместо sys.stdout. Но это происходит, даже когда я говорю sys.stderr = sys.stdout в конце моего модуля.

Так есть ли способ использовать doctests для проверки предупреждений? Я не могу найти упоминания об этом так или иначе в документации или в моем поиске в Google.

Ответы [ 6 ]

4 голосов
/ 19 марта 2010

Это не самый элегантный способ сделать это, но он работает для меня:

from warnings import warn

class Foo(object):
    """
    Instantiating Foo always gives a warning:

    >>> import sys; sys.stderr = sys.stdout
    >>> foo = Foo() # doctest:+ELLIPSIS
    /.../testdocs.py:14: UserWarning: Boo!
      warn("Boo!", UserWarning)
    """

    def __init__(self):
        warn("Boo!", UserWarning)

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

Это, вероятно, не будет работать в Windows, так как путь, указанный в выводе UserWarning, должен начинаться с косой черты, как я написал этот тест. Возможно, вам удастся выяснить какое-то лучшее заклинание директивы ELLIPSIS, но я не смог.

3 голосов
/ 20 марта 2010

Разделы Testing Warnings документации Python посвящены этой теме. Тем не менее, чтобы подвести итог, у вас есть два варианта:

(A) Используйте менеджер контекста catch_warnings

Это рекомендуемый курс в официальной документации. Однако менеджер контекста catch_warnings появился только в Python 2.6.

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings(record=True) as w:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")
    # Trigger a warning.
    fxn()
    # Verify some things
    assert len(w) == 1
    assert issubclass(w[-1].category, DeprecationWarning)
    assert "deprecated" in str(w[-1].message)

(B) Обновление предупреждений до ошибок

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

import warnings


def fxn():
    warnings.warn("deprecated", DeprecationWarning)


if __name__ == '__main__':
    warnings.simplefilter("error", DeprecationWarning)

    try:
        fxn()
    except DeprecationWarning:
        print "Pass"
    else:
        print "Fail"
    finally:
        warnings.simplefilter("default", DeprecationWarning)
1 голос
/ 19 марта 2010

Проблема, с которой вы сталкиваетесь, заключается в том, что warnings.warn() вызывает warnings.showwarning(), который записывает результат warnings.formatwarning() в файл, по умолчанию sys.stderr.

(см .: http://docs.python.org/library/warnings.html#warnings.showwarning)

Если вы используете Python 2.6, вы можете использовать менеджер контекста warnings.catch_warnings(), чтобы легко изменить способ обработки предупреждений, включая временную замену реализации warnings.showwarning() для записи в sys.stdout. Это был бы правильный способ справиться с чем-то вроде этого.

(см .: http://docs.python.org/library/warnings.html#available-context-managers)

Если вы хотите быстрый и грязный хак, соберите декоратор, который перенаправляет с sys.stderr на sys.stdout:

def stderr_to_stdout(func):
    def wrapper(*args):
        stderr_bak = sys.stderr
        sys.stderr = sys.stdout
        try:
            return func(*args)
        finally:
            sys.stderr = stderr_bak
    return wrapper

Затем вы можете вызвать декорированную функцию в вашем тесте:

from warnings import warn
from utils import stderr_to_stdout

class Foo(object):
    """
    Instantiating Foo always gives a warning:

    >>> @stderr_to_stdout
    ... def make_me_a_foo():
    ...     Foo()
    ...
    >>> make_me_a_foo()
    testdocs.py:18: UserWarning: 
      warn("Boo!", UserWarning)
    >>>
    """ 
    def __init__(self):
        warn("Boo!", UserWarning)

Что проходит:

$ python -m doctest testdocs.py -v
Trying:
    @stderr_to_stdout
    def make_me_a_foo():
        Foo()
Expecting nothing
ok
Trying:
    make_me_a_foo()
Expecting:
    testdocs.py:18: UserWarning: Boo!
      warn("Boo!", UserWarning)
ok
[...]
2 passed and 0 failed.
0 голосов
/ 21 марта 2010

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

0 голосов
/ 19 марта 2010

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

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

В качестве альтернативы, вы могли бы просто поднять полностью настраиваемого бегуна-докера, но я полагаю, вы бы предпочли этого избежать. :)

0 голосов
/ 10 марта 2010

документы предполагают, что вы можете передать -Wd при запуске doctest, чтобы всегда вызывать предупреждения.

...