Как написать модульный тест, который завершается ошибкой только в том случае, если функция не генерирует ожидаемое исключение?






Используйте TestCase.assertRaises (или TestCase.failUnlessRaises) из модуля unittest, например:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)
Обратите внимание, что для передачи аргументов myfunc вам необходимо добавить их в качестве аргументов в вызов assertRaises. См. Ответ Дэрила Спитцера.
есть ли способ разрешить несколько типов исключений?
Вы можете использовать встроенные исключения Python, чтобы быстро проверить утверждение; используя ответ @ Moe выше, например: self.assertRaises(TypeError, mymod.myfunc). Вы можете найти полный список встроенных исключений здесь: docs.python.org/3/library/exceptions.html#bltin-exceptions
То же самое для конструкторов классов тестирования: self.assertRaises(SomeCoolException, Constructor, arg1)
другой способ передачи аргументов - self.assertRaises(MyException, lambda: do_something(with=some, arguments))
Чтобы ответить на вопрос BUInvent, как сделать наоборот: просто позвоните mymod.myfunc() из своего теста. Если он возникает, это исключение не пройдет проверку. Это поведение по умолчанию для всего, что вы вызываете из теста.
Ваш код должен следовать этому шаблону (это тест стиля модуля 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 проверяет только возникновение исключения.
а метод self.fail принимает только один аргумент
Это кажется слишком сложным для тестирования, если функция вызывает исключение. Поскольку любое исключение, кроме этого исключения, приведет к ошибке теста, а отсутствие исключения приведет к сбою теста, похоже, единственная разница в том, что если вы получите другое исключение с assertRaises, вы получите ERROR вместо FAIL.
Код в моем предыдущем ответе можно упростить до:
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).
Я использую доктест [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
Мне нравится doctest, но я считаю, что он скорее дополняет, чем заменяет unittest.
Неужели doctest с меньшей вероятностью будет работать с автоматическим рефакторингом? Я полагаю, что инструмент рефакторинга, разработанный для python должен, должен знать о строках документации. Кто-нибудь может прокомментировать их опыт?
Я только что обнаружил, что Библиотека макетов предоставляет метод assertRaisesWithMessage () (в своем подклассе unittest.TestCase), который проверяет не только возникновение ожидаемого исключения, но и его появление с ожидаемым сообщением:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
'expected message',
mymod.myfunc)
К сожалению, он больше не предоставляет его .. Но приведенный выше ответ @Art (stackoverflow.com/a/3166985/1504046) дает тот же результат
Начиная с 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))
Я использую Python 2.7.10, и вышеперечисленное не работает; context.exception не выдает сообщения; это тип.
Также в Python 2.7 (по крайней мере, в моем 2.7.6) с использованием import unittest2 вам необходимо использовать str(), то есть self.assertTrue('This is broken' in str(context.exception)).
Две вещи: 1. Вы можете использовать assertIn вместо assertTrue. Например. self.assertIn ('Это не работает', context.exception) 2. В моем случае, используя 2.7.10, context.exception выглядит как массив символов. Использование str не работает. В итоге я сделал следующее: '' .join (context.exception) Итак, соберите: self.assertIn ('Это не работает', '' .join (context.exception))
Это нормально, что ваш метод засоряет тестовую консоль исключением Traceback? Как мне этого избежать?
Сейчас я использую 2.7.12, и он работает. Это правда, что context.exception по-прежнему является типом, но он работает, str (context.exception) не требуется.
позже я нашел другой способ получить сообщение как str исключения, это err = context.exception.message. А затем можно также использовать self.assertEqual (err, 'This is broken') для выполнения теста.
Или, если вас не волнует сообщение об исключении, вы можете упростить его до: with self.assertRaises(Exception): broken_function()
Попробуйте вместо этого self.assertTrue («Это не работает» в context.exception.value).
Вы можете использовать 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)
от: 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 возникает фактически и вызывает сбой теста. Проблема в том, что это именно то поведение, которое мы хотели: s.
Чтобы избежать этой ошибки, просто запустите функцию, используя лямбда в тестовом вызове:
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)) вызывает метод и возвращает результат. Вы хотите передать метод и любые аргументы и позволить модулю unittest вызвать его: 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!
How do you test that a Python function throws an exception?
How does one write a test that fails only if a function doesn't throw an expected exception?
Используйте метод 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>
И мы видим, что, как и ожидалось, попытка добавить 1 и '1' приводит к TypeError.
Для более подробного вывода попробуйте следующее:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Здесь много ответов. В коде показано, как мы можем создать исключение, как мы можем использовать это исключение в наших методах и, наконец, как вы можете проверить в модульном тесте, что возникают правильные исключения.
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()
Хотя все ответы прекрасны, я искал способ проверить, вызывает ли функция исключение, не полагаясь на фреймворки модульного тестирования и не создавая тестовых классов.
В итоге я написал следующее:
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
Поскольку я не видел подробного объяснения того, как проверить, есть ли у нас конкретное исключение среди списка принятых с помощью диспетчера контекста, или других деталей исключения, я добавлю свое (проверено на python 3.8).
Если я просто хочу проверить, что функция поднимается, например, TypeError, я бы написал:
with self.assertRaises(TypeError):
function_raising_some_exception(parameters)
Если я хочу проверить, что эта функция поднимает либо TypeError, либо IndexError, я бы написал:
with self.assertRaises((TypeError,IndexError)):
function_raising_some_exception(parameters)
И если мне нужно еще больше подробностей о поднятом исключении, я могу уловить его в таком контексте:
# Here I catch any exception
with self.assertRaises(Exception) as e:
function_raising_some_exception(parameters)
# Here I check actual exception 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])
Этот ответ исключительно (ха!) Полезен из-за проверки типа и предложения проверки чего-либо еще. Это помогло мне проверить явное сообщение об ошибке с self.assertEqual(e.exception.args[0], "Values cannot be a generator. Use list(generator) instead.",).
Ошибки, управляемые контекстом, являются лучшими. Они также позволяют проверить сообщение об ошибке!
Вы можете использовать диспетчер контекста для запуска неисправной функции и утверждения, что она вызывает исключение с определенным сообщением, используя assertRaisesMessage.
with self.assertRaisesMessage(SomeException,'Some error message e.g 404 Not Found'):
faulty_funtion()
assertRaisesMessage - это метод только Django, а не собственный метод класса Python Testcase, как показано в документах здесь, пожалуйста, отредактируйте свой ответ, чтобы прояснить это.
Для await / async aiounittest существует немного другой шаблон:
https://aiounittest.readthedocs.io/en/latest/asynctestcase.html#aiounittest.AsyncTestCase
async def test_await_async_fail(self):
with self.assertRaises(Exception) as e:
await async_one()
есть ли способ сделать наоборот? Как будто он терпит неудачу, только если функция генерирует исключение?