Como se escreve um unittest que falha apenas se uma função não lança uma exceção esperada?
Como se escreve um unittest que falha apenas se uma função não lança uma exceção esperada?
Respostas:
Use TestCase.assertRaises
(ou TestCase.failUnlessRaises
) do módulo mais compacto, por exemplo:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)
myfunc
você deve adicioná-los como argumentos à chamada assertRaises. Veja a resposta de Daryl Spitzer.
self.assertRaises(TypeError, mymod.myfunc)
. Você pode encontrar uma lista completa das exceções integradas
self.assertRaises(SomeCoolException, Constructor, arg1)
Desde o Python 2.7, você pode usar o gerenciador de contexto para obter o objeto Exception real lançado:
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
Em Python 3.5 , você tem que envolver context.exception
em str
, caso contrário, você vai ter umTypeError
self.assertTrue('This is broken' in str(context.exception))
context.exception
não transmite a mensagem; é um tipo.
import unittest2
, você precisa usar str()
, ie self.assertTrue('This is broken' in str(context.exception))
.
O código na minha resposta anterior pode ser simplificado para:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
E se a função funcionar com argumentos, basta passá-los para assertRaises como este:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
2.7.15
. Se afunction
in self.assertRaises(ExpectedException, afunction, arg1, arg2)
é o inicializador de classe, você precisa passar self
como o primeiro argumento, por exemplo: self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Como você testa se uma função Python lança uma exceção?
Como alguém escreve um teste que falha apenas se uma função não lança uma exceção esperada?
Use o self.assertRaises
método como um gerenciador de contexto:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
A abordagem de melhores práticas é bastante fácil de demonstrar em um shell Python.
A unittest
biblioteca
No Python 2.7 ou 3:
import unittest
No Python 2.6, você pode instalar um backport da unittest
biblioteca do 2.7 , chamado unittest2 , e apenas o pseudônimo de unittest
:
import unittest2 as unittest
Agora, cole no seu shell Python o seguinte teste de segurança de tipo 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')
Teste um usa assertRaises
como gerenciador de contexto, o que garante que o erro seja capturado e limpo adequadamente, enquanto registrado.
Também podemos escrevê-lo sem o gerenciador de contexto, veja o teste dois. O primeiro argumento seria o tipo de erro que você espera gerar, o segundo argumento, a função que você está testando e os argumentos e palavras-chave restantes serão passados para essa função.
Eu acho que é muito mais simples, legível e sustentável apenas para usar o gerenciador de contexto.
Para executar os testes:
unittest.main(exit=False)
No Python 2.6, você provavelmente precisará do seguinte :
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
E seu terminal deve gerar 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 '1'
resultado em um TypeError
.
Para uma saída mais detalhada, tente o seguinte:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Seu código deve seguir este padrão (este é um teste de estilo de módulo mais 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')
No Python <2.7, essa construção é útil para verificar valores específicos na exceção esperada. A função mais unittest assertRaises
verifica apenas se uma exceção foi levantada.
assertRaises
, obterá um ERRO ao invés de uma FALHA.
from: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/
Primeiro, aqui está a função correspondente (ainda dum: p) no arquivo 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
Aqui está o teste a ser executado (apenas este teste é inserido):
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 nossa função! Aqui está o que acontece ao tentar 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)
O TypeError é ativado no momento e gera uma falha no teste. O problema é que esse é exatamente o comportamento que queríamos: s.
Para evitar esse 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
Perfeito!
... e para mim também é perfeito !!
Muito obrigado Sr. Julien Lengrand-Lambert
Esta afirmação de teste realmente retorna um falso positivo . Isso acontece porque o lambda dentro de 'assertRaises' é a unidade que gera erro de tipo e não a função testada.
self.assertRaises(TypeError, df.square_value(self.false_int))
chama o método e retorna o resultado. O que você quer é passar o método e quaisquer argumentos e deixar o unittest para chamá-lo:self.assertRaises(TypeError, df.square_value, self.false_int)
Você pode criar seu próprio contextmanager
para verificar se a exceção foi gerada.
import contextlib
@contextlib.contextmanager
def raises(exception):
try:
yield
except exception as e:
assert True
else:
assert False
E então 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
, isso 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
unittest
módulo!
Eu uso o doctest [1] em quase todos os lugares porque gosto do fato de documentar e testar minhas funções ao mesmo tempo.
Dê uma olhada neste 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 na linha de comando, os dois casos de teste serão avaliados e verificados.
[1] Documentação do Python: 23.2 doctest - Testar exemplos interativos do Python
Acabei de descobrir que a biblioteca Mock fornece um método assertRaisesWithMessage () (em sua subclasse unittest.TestCase), que verificará não apenas se a exceção esperada foi levantada, mas também com a mensagem esperada:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
'expected message',
mymod.myfunc)
Há muitas respostas aqui. O código mostra como podemos criar uma exceção, como podemos usar essa exceção em nossos métodos e, finalmente, como você pode verificar em um teste de unidade, 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()
Você pode usar assertRaises do módulo mais 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)
Embora todas as respostas estejam perfeitamente corretas, eu estava procurando uma maneira de testar se uma função gerava uma exceção sem depender de estruturas de teste de unidade e sem precisar escrever classes de teste.
Acabei escrevendo o seguinte:
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)
E falha na linha certa:
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