Como você testa se uma função Python lança uma exceção?

Respostas:

679

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)
Moe
fonte
9
existe uma maneira de fazer o oposto disso? Como ele falha apenas se a função lança a exceção?
BUInvent
67
Observe que, para passar argumentos, myfuncvocê deve adicioná-los como argumentos à chamada assertRaises. Veja a resposta de Daryl Spitzer.
Cbron
1
existe uma maneira de permitir vários tipos de exceção?
Dinesh
1
Você pode usar as exceções integradas do Python para testar rapidamente a asserção; usando @ resposta de Moe acima, por exemplo: self.assertRaises(TypeError, mymod.myfunc). Você pode encontrar uma lista completa das exceções integradas
Raymond Wachaga
3
A mesma coisa para testar construtores de classe:self.assertRaises(SomeCoolException, Constructor, arg1)
tschumann
476

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.exceptionem str, caso contrário, você vai ter umTypeError

self.assertTrue('This is broken' in str(context.exception))
Arte
fonte
6
Estou usando o Python 2.7.10, e o acima não funciona; context.exceptionnão transmite a mensagem; é um tipo.
LateCoder
6
Também no Python 2.7 (pelo menos no meu 2.7.6) usando import unittest2, você precisa usar str(), ie self.assertTrue('This is broken' in str(context.exception)).
Sohail Si
4
Duas coisas: 1. Você pode usar assertIn em vez de assertTrue. Eg self.assertIn ('This is broken', context.exception) 2. No meu caso, usando 2.7.10, context.exception parece ser uma matriz de caracteres. Usar str não funciona. Acabei fazendo o seguinte: '' .join (context.exception) Então, junte-se: self.assertIn ('This is broken', '' .join (context.exception))
blockcipher
1
É normal que seu método obstrua o console de teste com o Traceback da exceção? Como evito que isso aconteça?
MadPhysicist
1
mais tarde, encontrei outra maneira de obter a mensagem como str da exceção, é err = context.exception.message. E então pode usar também use self.assertEqual (err, 'Isso está quebrado') para fazer o teste.
Zhihong 11/05
326

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)
Daryl Spitzer
fonte
17
O segundo trecho sobre o que fazer quando o argumento é passado foi realmente útil.
Sabyasachi
Estou usando 2.7.15. Se afunctionin self.assertRaises(ExpectedException, afunction, arg1, arg2)é o inicializador de classe, você precisa passar selfcomo o primeiro argumento, por exemplo: self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Minh Tran
128

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?

Resposta curta:

Use o self.assertRaisesmétodo como um gerenciador de contexto:

    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'

Demonstração

A abordagem de melhores práticas é bastante fácil de demonstrar em um shell Python.

A unittestbiblioteca

No Python 2.7 ou 3:

import unittest

No Python 2.6, você pode instalar um backport da unittestbiblioteca do 2.7 , chamado unittest2 , e apenas o pseudônimo de unittest:

import unittest2 as unittest

Testes de exemplo

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.

Executando os testes

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 1e um '1'resultado em um TypeError.


Para uma saída mais detalhada, tente o seguinte:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Aaron Hall
fonte
56

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 assertRaisesverifica apenas se uma exceção foi levantada.

Daryl Spitzer
fonte
3
e método self.fail tem apenas um argumento
mdob
3
Isso parece excessivamente complicado para testar se uma função gera uma exceção. Como qualquer exceção que não seja a exceção irá errar no teste e não lançar uma exceção falhará no teste, parece que a única diferença é que, se você receber uma exceção diferente assertRaises, obterá um ERRO ao invés de uma FALHA.
unflores
23

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.

macm
fonte
10
Apenas uma nota, você não precisa da lambda. A linha 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)
Roman Kutlak
Muito obrigado. funciona perfeitamente
Chandan Kumar 22/04
14

Você pode criar seu próprio contextmanagerpara 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 raisesassim:

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
Pigueiras
fonte
1
Obrigado por postar uma resposta que não requer o unittestmódulo!
Sherwood Callaway
10

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

pi.
fonte
4
Adoro o doctest, mas acho que suplementa em vez de substituir o mais unittest.
precisa saber é o seguinte
2
É menos provável que o doctest seja agradável com a refatoração automática? Suponho que uma ferramenta de refatoração projetada para python deve estar ciente dos documentos. Alguém pode comentar com sua experiência?
kdbanman
6

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)
Daryl Spitzer
fonte
Infelizmente, ele não fornece mais isso .. Mas a resposta acima de @Art ( stackoverflow.com/a/3166985/1504046 ) dá o mesmo resultado
Rmatt
6

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()
Arindam Roychowdhury
fonte
3

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)
Bruno Carvalho
fonte
-5

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
RUser4512
fonte