Как написать unittest, который не проходит, только если функция не выдает ожидаемое исключение?
Как написать unittest, который не проходит, только если функция не выдает ожидаемое исключение?
Ответы:
Используйте TestCase.assertRaises
(или TestCase.failUnlessRaises
) из модуля unittest, например:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)
myfunc
вам необходимо добавить их в качестве аргументов в вызов assertRaises. Смотрите ответ Дэрила Спитцера.
self.assertRaises(TypeError, mymod.myfunc)
. Вы можете найти полный список встроенных исключений здесь: docs.python.org/3/library/exceptions.html#bltin-exceptions
self.assertRaises(SomeCoolException, Constructor, arg1)
Начиная с Python 2.7 вы можете использовать менеджер контекста, чтобы получить реальный объект Exception:
import unittest
def broken_function():
raise Exception('This is broken')
class MyTestCase(unittest.TestCase):
def test(self):
with self.assertRaises(Exception) as context:
broken_function()
self.assertTrue('This is broken' in context.exception)
if __name__ == '__main__':
unittest.main()
http://docs.python.org/dev/library/unittest.html#unittest.TestCase.assertRaises
В Python 3.5 , вы должны обернуть context.exception
в str
противном случае вы получитеTypeError
self.assertTrue('This is broken' in str(context.exception))
context.exception
не дает сообщение; это тип.
import unittest2
, вам нужно использовать str()
, то есть self.assertTrue('This is broken' in str(context.exception))
.
Код в моем предыдущем ответе может быть упрощен до:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
И если функция принимает аргументы, просто передайте их в assertRaises следующим образом:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
2.7.15
. Если afunction
в self.assertRaises(ExpectedException, afunction, arg1, arg2)
это инициализатор класса, вам нужно передать self
в качестве первого аргумента, например, self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Как вы проверяете, что функция Python генерирует исключение?
Как написать тест, который не проходит, только если функция не выдает ожидаемое исключение?
Используйте self.assertRaises
метод как менеджер контекста:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
Подход наилучшей практики довольно легко продемонстрировать в оболочке Python.
unittest
библиотека
В Python 2.7 или 3:
import unittest
В Python 2.6 вы можете установить бэкпортunittest
библиотеки 2.7 , называемый unittest2 , и просто псевдоним, который будет следующим unittest
:
import unittest2 as unittest
Теперь вставьте в вашу оболочку Python следующий тест безопасности типов Python:
class MyTestCase(unittest.TestCase):
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
def test_2_cannot_add_int_and_str(self):
import operator
self.assertRaises(TypeError, operator.add, 1, '1')
Тест один использует assertRaises
в качестве диспетчера контекста, который гарантирует, что ошибка правильно перехвачена и исправлена во время записи.
Мы могли бы также написать это без менеджера контекста, см. Тест два. Первым аргументом будет тип ошибки, который вы ожидаете вызвать, второй аргумент, функция, которую вы тестируете, а остальные аргументы и аргументы ключевых слов будут переданы этой функции.
Я думаю, что гораздо проще, удобочитаемее и удобнее в использовании просто использование диспетчера контекста.
Чтобы запустить тесты:
unittest.main(exit=False)
В Python 2.6 вам, вероятно, понадобится следующее :
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
И ваш терминал должен вывести следующее:
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s
OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>
И мы видим, что, как мы и ожидаем, попытка добавить a 1
и '1'
результат в TypeError
.
Для более подробного вывода попробуйте это:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Ваш код должен следовать этому шаблону (это тест стиля модуля unittest):
def test_afunction_throws_exception(self):
try:
afunction()
except ExpectedException:
pass
except Exception:
self.fail('unexpected exception raised')
else:
self.fail('ExpectedException not raised')
На Python <2.7 эта конструкция полезна для проверки конкретных значений в ожидаемом исключении. Функция unittest assertRaises
только проверяет, возникло ли исключение.
assertRaises
вы получите ОШИБКУ вместо ОТКАЗА.
от: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/
Во-первых, вот соответствующая (все еще dum: p) функция в файле dum_function.py:
def square_value(a):
"""
Returns the square value of a.
"""
try:
out = a*a
except TypeError:
raise TypeError("Input should be a string:")
return out
Вот тест, который нужно выполнить (вставлен только этот тест):
import dum_function as df # import function module
import unittest
class Test(unittest.TestCase):
"""
The class inherits from unittest
"""
def setUp(self):
"""
This method is called before each test
"""
self.false_int = "A"
def tearDown(self):
"""
This method is called after each test
"""
pass
#---
## TESTS
def test_square_value(self):
# assertRaises(excClass, callableObj) prototype
self.assertRaises(TypeError, df.square_value(self.false_int))
if __name__ == "__main__":
unittest.main()
Теперь мы готовы проверить нашу функцию! Вот что происходит при попытке запустить тест:
======================================================================
ERROR: test_square_value (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_dum_function.py", line 22, in test_square_value
self.assertRaises(TypeError, df.square_value(self.false_int))
File "/home/jlengrand/Desktop/function.py", line 8, in square_value
raise TypeError("Input should be a string:")
TypeError: Input should be a string:
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
TypeError вызывается actullay и генерирует тестовый сбой. Проблема в том, что это именно то поведение, которое мы хотели: с.
Чтобы избежать этой ошибки, просто запустите функцию, используя лямбду в тестовом вызове:
self.assertRaises(TypeError, lambda: df.square_value(self.false_int))
Окончательный вывод:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Отлично !
... и для меня тоже идеально !!
Большое спасибо господин Жюльен Ленгранд-Ламберт
Этот тест assert фактически возвращает ложный положительный результат . Это происходит потому, что лямбда внутри 'assertRaises' является единицей, которая вызывает ошибку типа, а не проверенной функцией.
self.assertRaises(TypeError, df.square_value(self.false_int))
вызывает метод и возвращает результат. Вам нужно передать метод и любые аргументы и позволить юнит-тесту вызвать его:self.assertRaises(TypeError, df.square_value, self.false_int)
Вы можете создать свой собственный, contextmanager
чтобы проверить, возникло ли исключение.
import contextlib
@contextlib.contextmanager
def raises(exception):
try:
yield
except exception as e:
assert True
else:
assert False
И тогда вы можете использовать raises
как это:
with raises(Exception):
print "Hola" # Calls assert False
with raises(Exception):
raise Exception # Calls assert True
Если вы используете pytest
, эта вещь уже реализована. Ты можешь сделатьpytest.raises(Exception)
:
Пример:
def test_div_zero():
with pytest.raises(ZeroDivisionError):
1/0
И результат:
pigueiras@pigueiras$ py.test
================= test session starts =================
platform linux2 -- Python 2.6.6 -- py-1.4.20 -- pytest-2.5.2 -- /usr/bin/python
collected 1 items
tests/test_div_zero.py:6: test_div_zero PASSED
unittest
модуля!
Я использую doctest [1] почти везде, потому что мне нравится тот факт, что я документирую и тестирую свои функции одновременно.
Посмотрите на этот код:
def throw_up(something, gowrong=False):
"""
>>> throw_up('Fish n Chips')
Traceback (most recent call last):
...
Exception: Fish n Chips
>>> throw_up('Fish n Chips', gowrong=True)
'I feel fine!'
"""
if gowrong:
return "I feel fine!"
raise Exception(something)
if __name__ == '__main__':
import doctest
doctest.testmod()
Если вы поместите этот пример в модуль и запустите его из командной строки, оба контрольных примера будут оценены и проверены.
[1] Документация по Python: 23.2 doctest - тестирование интерактивных примеров Python
Я только что обнаружил, что библиотека Mock предоставляет метод assertRaisesWithMessage () (в своем подклассе unittest.TestCase), который будет проверять не только то, что вызывается ожидаемое исключение, но и то, что оно вызывается с ожидаемым сообщением:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
'expected message',
mymod.myfunc)
Здесь много ответов. Код показывает, как мы можем создать Исключение, как мы можем использовать это исключение в наших методах и, наконец, как вы можете проверить в модульном тесте правильные возбуждаемые исключения.
import unittest
class DeviceException(Exception):
def __init__(self, msg, code):
self.msg = msg
self.code = code
def __str__(self):
return repr("Error {}: {}".format(self.code, self.msg))
class MyDevice(object):
def __init__(self):
self.name = 'DefaultName'
def setParameter(self, param, value):
if isinstance(value, str):
setattr(self, param , value)
else:
raise DeviceException('Incorrect type of argument passed. Name expects a string', 100001)
def getParameter(self, param):
return getattr(self, param)
class TestMyDevice(unittest.TestCase):
def setUp(self):
self.dev1 = MyDevice()
def tearDown(self):
del self.dev1
def test_name(self):
""" Test for valid input for name parameter """
self.dev1.setParameter('name', 'MyDevice')
name = self.dev1.getParameter('name')
self.assertEqual(name, 'MyDevice')
def test_invalid_name(self):
""" Test to check if error is raised if invalid type of input is provided """
self.assertRaises(DeviceException, self.dev1.setParameter, 'name', 1234)
def test_exception_message(self):
""" Test to check if correct exception message and code is raised when incorrect value is passed """
with self.assertRaises(DeviceException) as cm:
self.dev1.setParameter('name', 1234)
self.assertEqual(cm.exception.msg, 'Incorrect type of argument passed. Name expects a string', 'mismatch in expected error message')
self.assertEqual(cm.exception.code, 100001, 'mismatch in expected error code')
if __name__ == '__main__':
unittest.main()
Вы можете использовать assertRaises из модуля unittest
import unittest
class TestClass():
def raises_exception(self):
raise Exception("test")
class MyTestCase(unittest.TestCase):
def test_if_method_raises_correct_exception(self):
test_class = TestClass()
# note that you dont use () when passing the method to assertRaises
self.assertRaises(Exception, test_class.raises_exception)
Поскольку я не видел подробного объяснения о том, как проверить, получено ли конкретное исключение из списка принятых с помощью контекстного менеджера, или других подробностей исключения, я добавлю свои (проверено на python 3.8).
Если я просто хочу проверить, например TypeError
, что функция поднимается , я бы написал:
with self.assertRaises(TypeError):
function_raising_some_exception(parameters)
Если я хочу проверить, что функция вызывает или TypeError
или IndexError
, я бы написал: с self.assertRaises ((TypeError, IndexError)): function_raising_some_exception (параметры)
И если я захочу узнать больше подробностей об Исключении, я могу уловить его в таком контексте:
# Here I catch any exception
with self.assertRaises(Exception) as e:
function_raising_some_exception(parameters)
# Here I check actual eception type (but I could check anything else about that specific exception, like it's actual message or values stored in the exception)
self.assertTrue(type(e.exception) in [TypeError,MatrixIsSingular])
Несмотря на то, что все ответы в порядке, я искал способ проверить, вызвала ли функция исключение, не полагаясь на рамки модульного тестирования и не создавая тестовые классы.
В итоге я написал следующее:
def assert_error(e, x):
try:
e(x)
except:
return
raise AssertionError()
def failing_function(x):
raise ValueError()
def dummy_function(x):
return x
if __name__=="__main__":
assert_error(failing_function, 0)
assert_error(dummy_function, 0)
И это не на правильной линии:
Traceback (most recent call last):
File "assert_error.py", line 16, in <module>
assert_error(dummy_function, 0)
File "assert_error.py", line 6, in assert_error
raise AssertionError()
AssertionError