Я хотел бы использовать 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
чтобы выполнить doctest в моем классе и удостовериться, что предупреждение печатается, я добираюсь:
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.
Это не самый элегантный способ сделать это, но он работает для меня:
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, но я не смог.
docs предполагают, что вы можете передать -Wd
при запуске doctest, чтобы всегда вызывать предупреждения.
Возможно, вы могли бы попробовать высмеять (напечатать!) проблемный бит. Я признаю, что это внесет некоторый беспорядок в документацию, но, возможно, стоит попробовать.
Если вы хотите использовать этот подход, сохраняя текущий синтаксис, возможно, вы могли бы попробовать реализовать пользовательскую обертку для doctest, которая генерирует недостающий код, а затем выполняет исправленные тесты. Если возможно, этого лучше избегать.
В качестве альтернативы вы можете просто создать полностью собственный doctest runner, но я полагаю, что вы предпочтете избежать этого. :)
.Проблема, с которой вы столкнулись, заключается в том, что 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
Затем вы можете вызвать декорированную функцию в вашем doctest:
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.
Это один из примеров того, почему доктесты подходят не для всех тестов. Если у вас есть встроенные примеры в ваших строках документации, и их нужно протестировать, это одно, но, как вы обнаружили, есть варианты поведения, которые вы хотите проверить, и которые не лучше всего делать с сопоставлением строк. И случаи, когда вам не нужно загромождать строку документации всеми механизмами тестирования.
Разделы Предупреждения о тестировании документации по Python посвящены этому разделу. Однако, подводя итог, у вас есть два варианта:
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)
Если предупреждение не было замечено ранее и, следовательно, было зарегистрировано в реестре предупреждений, то вы можете установить предупреждения для создания исключений и их перехвата.
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)