Como você testar se uma função Python lança uma exceção?
-
02-07-2019 - |
Pergunta
Como se escrever uma unittest que não somente se uma função não lançar uma exceção esperado?
Solução
Use TestCase.assertRaises
(ou TestCase.failUnlessRaises
) do módulo unittest , por exemplo:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)
Outras dicas
Desde Python 2.7, você pode usar o gerente contexto costuma ficar o objeto exceção real lançada:
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/ biblioteca / unittest.html # unittest.TestCase.assertRaises
Em Python 3.5 , você tem que embrulhar context.exception
em str
, caso contrário, você vai ter um TypeError
self.assertTrue('This is broken' in str(context.exception))
O código na minha resposta anterior pode ser simplificada para:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
E se AFunction leva argumentos, apenas passá-los em assertRaises como este:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
Como você testar que uma função Python lança uma exceção?
Como se escreve um teste que falhar somente se uma função não joga uma exceção esperado?
Resposta curta:
Use o método self.assertRaises
como um gerente de contexto:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
Demonstração
A melhor abordagem prática é bastante fácil de demonstrar em um shell Python.
A biblioteca unittest
Em Python 2,7 ou 3:
import unittest
Em Python 2.6, você pode instalar um backport da biblioteca unittest
2.7 de, chamado unittest2 , e apenas alias que como unittest
:
import unittest2 as unittest
Exemplo testa ??h1>
Agora, cole em seu Python desembolsar o seguinte teste do tipo-segurança do 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')
Test uma utilidades assertRaises
como um gerente de contexto, o que garante que o erro está devidamente capturados e limpas, enquanto gravado.
Nós também poderia escrever sem o gerente contexto, ver teste de dois. O primeiro argumento seria o tipo de erro que você espera aumento, o segundo argumento, a função que você está testando, e os argumentos restantes e argumentos de palavra-chave serão passados ??para essa função.
Eu acho que é muito mais simples, legível e de fácil manutenção para apenas usar o gerenciador de contexto.
Executando os testes
Para executar os testes:
unittest.main(exit=False)
Em Python 2.6, você provavelmente vai necessário o seguinte :
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
E o seu terminal deve saída o seguinte:
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s
OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>
E vemos que, como esperamos, tentando adicionar um 1
e um resultado '1'
em um TypeError
.
Para obter mais detalhado saída, tente o seguinte:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
O código deve seguir esse padrão (este é um unittest teste estilo módulo):
def test_afunction_throws_exception(self):
try:
afunction()
except ExpectedException:
pass
except Exception as e:
self.fail('Unexpected exception raised:', e)
else:
self.fail('ExpectedException not raised')
Em Python <2.7 esta construção é útil para verificar se há valores específicos na exceção prevista. O unittest assertRaises
função somente verifica se uma exceção foi levantada.
De: http://www.lengrand.fr/2011 / 12 / pythonunittest-assertraises-raises de erros /
Em primeiro lugar, aqui é o correspondente (ainda dum: p), em função dum_function.py arquivo:
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
Aqui está o teste a ser realizado (é inserida apenas este teste):
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()
Agora estamos prontos para testar a nossa função! Aqui é o que acontece quando se tenta executar o teste:
======================================================================
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)
A TypeError é actullay levantada, e gera uma falha no teste. O problema é que este é exatamente o comportamento que queríamos:. S
Para evitar este erro, basta executar a função usando lambda na chamada de teste:
self.assertRaises(TypeError, lambda: df.square_value(self.false_int))
A saída final:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Perfect!
... e para mim é perfeito também !!
Thansk um monte Sr. Julien Lengrand-Lambert
Você pode construir seu próprio contextmanager
para verificar se a exceção foi levantada.
import contextlib
@contextlib.contextmanager
def raises(exception):
try:
yield
except exception as e:
assert True
else:
assert False
E, em seguida, você pode usar raises
assim:
with raises(Exception):
print "Hola" # Calls assert False
with raises(Exception):
raise Exception # Calls assert True
Se você estiver usando pytest
, essa coisa já está implementado. Você pode fazer pytest.raises(Exception)
:
Exemplo:
def test_div_zero():
with pytest.raises(ZeroDivisionError):
1/0
E o resultado:
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
Eu uso doctest [1] quase todos os lugares, porque eu gosto do fato de que eu documento e testar minhas funções ao mesmo tempo.
Tenha um olhar para este código:
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()
Se você colocar este exemplo em um módulo e executá-lo a partir da linha de comando ambos os casos de teste são avaliados e controlados.
[1] Python documentação: 23.2 doctest - exemplos de teste interativo Python
Tenha um olhar para o método assertRaises de o módulo unittest
.
Eu só descobri que a Mock biblioteca fornece um método assertRaisesWithMessage () ( em sua subclasse unittest.TestCase), que irá verificar não só que a exceção prevista é levantada, mas também que é levantada com a mensagem esperado:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
'expected message',
mymod.myfunc)
Você pode usar assertRaises do módulo 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)
Existem muitas respostas aqui. Os shows de código como podemos criar uma exceção, como podemos usar essa exceção em nossos métodos e, finalmente, como você pode verificar em um unittest, as exceções corretas sendo levantadas.
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()