Как проверить с Python unittest, что предупреждение было брошено?
У меня есть следующая функция в Python, и я хочу проверить с unittest, что если функция получает 0 в качестве аргумента, она выдает предупреждение. Я уже пробовал assertRaises, но так как я не выдвигаю предупреждение, это не работает.
def isZero(i):
if i != 0:
print "OK"
else:
warning = Warning("the input is 0!")
print warning
return i
Ответы
Ответ 1
Вы можете использовать контекстный менеджер catch_warnings
. По сути, это позволяет вам высмеивать обработчик предупреждений, чтобы вы могли проверить информацию об этом предупреждении. См. Официальные документы для более полного объяснения и примерного тестового кода.
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)
Ответ 2
Начиная с Python 3.2, вы можете просто использовать метод assertWarns()
.
with self.assertWarns(Warning):
do_something()
Ответ 3
Вы можете написать свою собственную функцию assertWarns, чтобы инкапсулировать контекст catch_warnings. Я только что реализовал его следующим образом: mixin:
class WarningTestMixin(object):
'A test which checks if the specified warning was raised'
def assertWarns(self, warning, callable, *args, **kwds):
with warnings.catch_warnings(record=True) as warning_list:
warnings.simplefilter('always')
result = callable(*args, **kwds)
self.assertTrue(any(item.category == warning for item in warning_list))
Пример использования:
class SomeTest(WarningTestMixin, TestCase):
'Your testcase'
def test_something(self):
self.assertWarns(
UserWarning,
your_function_which_issues_a_warning,
5, 10, 'john', # args
foo='bar' # kwargs
)
Тест пройдет, если хотя бы одно из предупреждений, выданных your_function
, имеет тип UserWarning.
Ответ 4
@ire_and_curses answer весьма полезен и, я думаю, канонический. Вот еще один способ сделать то же самое. Для этого требуется Майкл Фоорд Mock
library.
import unittest, warnings
from mock import patch_object
def isZero( i):
if i != 0:
print "OK"
else:
warnings.warn( "the input is 0!")
return i
class Foo(unittest.TestCase):
@patch_object(warnings, 'warn')
def test_is_zero_raises_warning(self, mock_warn):
isZero(0)
self.assertTrue(mock_warn.called)
if __name__ == '__main__':
unittest.main()
Отличный patch_object
позволяет вам изловить метод warn
.
Ответ 5
Одна проблема с подходом warnings.catch_warnings
заключается в том, что предупреждения, создаваемые в разных тестах, могут взаимодействовать странным образом через глобальное состояние, сохраняемое в атрибутах __warningregistry__
.
Для решения этой проблемы мы должны очистить атрибут __warningregistry__
каждого модуля перед каждым тестом, проверяющим предупреждения.
class MyTest(unittest.TestCase):
def setUp(self):
# The __warningregistry__ need to be in a pristine state for tests
# to work properly.
for v in sys.modules.values():
if getattr(v, '__warningregistry__', None):
v.__warningregistry__ = {}
def test_something(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always", MySpecialWarning)
...
self.assertEqual(len(w), 1)
self.assertIsInstance(w[0].message, MySpecialWarning)
Именно так реализован метод Python 3 assertWarns()
.