Come si verifica che una funzione Python genera un'eccezione?


Risposte:


679

Utilizzare TestCase.assertRaises(o TestCase.failUnlessRaises) dal modulo unittest, ad esempio:

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)

9
c'è un modo per fare il contrario di questo? Ti piace solo se la funzione genera l'eccezione?
BUInvent

67
Si noti che per passare argomenti myfuncè necessario aggiungerli come argomenti alla chiamata assertRaises. Vedi la risposta di Daryl Spitzer.
cbron,

1
c'è un modo per consentire più tipi di eccezione?
Dinesh,

1
È possibile utilizzare le eccezioni integrate di Python per testare rapidamente l'asserzione; utilizzando @ risposta di Moe sopra, per esempio: self.assertRaises(TypeError, mymod.myfunc). Puoi trovare un elenco completo delle Eccezioni integrate qui: docs.python.org/3/library/exceptions.html#bltin-exceptions
Raymond Wachaga

3
Stessa cosa per testare i costruttori di classe:self.assertRaises(SomeCoolException, Constructor, arg1)
tschumann,

476

Da Python 2.7 è possibile utilizzare il Gestore di contesto per ottenere l'oggetto reale dell'eccezione generato:

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


In Python 3.5 , si deve avvolgere context.exceptionin str, altrimenti si otterrà unTypeError

self.assertTrue('This is broken' in str(context.exception))

6
Sto usando Python 2.7.10 e quanto sopra non funziona; context.exceptionnon dà il messaggio; è un tipo.
LateCoder,

6
Anche in Python 2.7 (almeno nel mio 2.7.6) usando import unittest2, devi usare str(), cioè self.assertTrue('This is broken' in str(context.exception)).
Sohail Si,

4
Due cose: 1. È possibile utilizzare assertIn anziché assertTrue. Ad esempio self.assertIn ('This is broken', context.exception) 2. Nel mio caso, usando 2.7.10, context.exception sembra essere un array di caratteri. L'uso di str non funziona. Ho finito per fare questo: '' .join (context.exception) Quindi, metti insieme: self.assertIn ('Questo è rotto', '' .join (context.exception))
blockcipher

1
È normale che il tuo metodo blocchi la console di test con il Traceback dell'eccezione? Come posso evitare che ciò accada?
MadPhysicist,

1
più tardi ho trovato un altro modo per ottenere il messaggio come str dell'eccezione, è err = context.exception.message. E poi puoi usare anche self.assertEqual (err, 'Questo è rotto') per fare il test.
Zhihong,

326

Il codice nella mia risposta precedente può essere semplificato per:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction)

E se afunction accetta argomenti, passali in assertRaises in questo modo:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)

17
Il secondo suggerimento su cosa fare quando si passa alla discussione è stato davvero utile.
Sabyasachi,

Sto usando 2.7.15. Se afunctionin self.assertRaises(ExpectedException, afunction, arg1, arg2)è l'inizializzatore di classe, è necessario passare selfcome primo argomento, ad es. self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Minh Tran,

128

Come si verifica che una funzione Python genera un'eccezione?

Come si può scrivere un test che fallisce solo se una funzione non genera un'eccezione prevista?

Risposta breve:

Utilizzare il self.assertRaisesmetodo come gestore di contesto:

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

Dimostrazione

L'approccio delle migliori pratiche è abbastanza facile da dimostrare in una shell Python.

La unittestbiblioteca

In Python 2.7 o 3:

import unittest

In Python 2.6, puoi installare un backport della unittestlibreria 2.7 , chiamato unittest2 , e solo alias come unittest:

import unittest2 as unittest

Test di esempio

Ora, incolla nella tua shell Python il seguente test di sicurezza del tipo di 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')

Il test uno utilizza assertRaisescome gestore di contesto, che garantisce che l'errore venga correttamente rilevato e ripulito, mentre viene registrato.

Potremmo anche scriverlo senza il gestore del contesto, vedi test due. Il primo argomento sarebbe il tipo di errore che ci si aspetta di generare, il secondo argomento, la funzione che si sta testando e gli argomenti rimanenti e gli argomenti della parola chiave verranno passati a quella funzione.

Penso che sia molto più semplice, leggibile e gestibile solo per usare il gestore del contesto.

Esecuzione dei test

Per eseguire i test:

unittest.main(exit=False)

In Python 2.6, probabilmente avrai bisogno di quanto segue :

unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))

E il tuo terminale dovrebbe produrre quanto segue:

..
----------------------------------------------------------------------
Ran 2 tests in 0.007s

OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>

E lo vediamo come ci aspettiamo, tentando di aggiungere ae 1un '1'risultato in a TypeError.


Per un output più dettagliato, prova questo:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))

56

Il codice dovrebbe seguire questo modello (questo è un test di stile del modulo 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')

Su Python <2.7 questo costrutto è utile per verificare valori specifici nell'eccezione prevista. La funzione unittest assertRaisesverifica solo se è stata sollevata un'eccezione.


3
e il metodo self.fail accetta solo un argomento
mdob,

3
Questo sembra eccessivamente complicato per il test se una funzione genera un'eccezione. Dal momento che qualsiasi eccezione diversa da quella eccezione guiderà il test e il mancato lancio di un'eccezione fallirà il test, sembra che l'unica differenza sia che se si ottiene un'eccezione diversa assertRaisessi otterrà un ERRORE anziché un GUASTO.
chiarisce il

23

da: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/

Innanzitutto, ecco la funzione corrispondente (ancora dum: p) nel file 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

Ecco il test da eseguire (è inserito solo questo test):

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()

Ora siamo pronti per testare la nostra funzione! Ecco cosa succede quando si tenta di eseguire il test:

======================================================================
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 generato e genera un errore di test. Il problema è che questo è esattamente il comportamento che volevamo: s.

Per evitare questo errore, esegui semplicemente la funzione usando lambda nella chiamata di prova:

self.assertRaises(TypeError, lambda: df.square_value(self.false_int))

L'output finale:

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Perfetto !

... e anche per me è perfetto !!

Grazie mille Julien Lengrand-Lambert


Questa affermazione del test in realtà restituisce un falso positivo . Ciò accade perché la lambda all'interno di 'assertRaises' è l'unità che genera l'errore di tipo e non la funzione testata.


10
Solo una nota, non hai bisogno della lambda. La linea self.assertRaises(TypeError, df.square_value(self.false_int))chiama il metodo e restituisce il risultato. Quello che vuoi è passare il metodo e tutti gli argomenti e lasciare che sia il più semplice a chiamarlo:self.assertRaises(TypeError, df.square_value, self.false_int)
Roman Kutlak

Molte grazie. funziona perfettamente
Chandan Kumar il

14

Puoi crearne uno tuo contextmanagerper verificare se l'eccezione è stata sollevata.

import contextlib

@contextlib.contextmanager
def raises(exception):
    try:
        yield 
    except exception as e:
        assert True
    else:
        assert False

E quindi puoi usare in raisesquesto modo:

with raises(Exception):
    print "Hola"  # Calls assert False

with raises(Exception):
    raise Exception  # Calls assert True

Se stai usando pytest, questa cosa è già implementata. Puoi fare pytest.raises(Exception):

Esempio:

def test_div_zero():
    with pytest.raises(ZeroDivisionError):
        1/0

E il risultato:

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

1
Grazie per aver pubblicato una risposta che non richiede il unittestmodulo!
Sherwood Callaway,

10

Uso doctest [1] quasi ovunque perché mi piace il fatto di documentare e testare le mie funzioni allo stesso tempo.

Dai un'occhiata a questo codice:

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 si inserisce questo esempio in un modulo ed lo si esegue dalla riga di comando, entrambi i casi di test vengono valutati e verificati.

[1] Documentazione Python: 23.2 doctest - Prova esempi interattivi di Python


4
Adoro doctest, ma trovo integratori piuttosto che sostituisce unittest.
TimothyAWiseman,

2
Doctest ha meno probabilità di giocare bene con il refactoring automatico? Suppongo che uno strumento di refactoring progettato per Python dovrebbe essere a conoscenza delle dotstring. Qualcuno può commentare dalla loro esperienza?
kdbanman,

6

Ho appena scoperto che la libreria Mock fornisce un metodo assertRaisesWithMessage () (nella sua sottoclasse unittest.TestCase), che controllerà non solo che venga sollevata l'eccezione prevista, ma anche che venga sollevata con il messaggio previsto:

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)

Purtroppo, non fornisce faccio più .. Ma la risposta di cui sopra di @Art ( stackoverflow.com/a/3166985/1504046 ) dà lo stesso risultato
Rmatt

6

Ci sono molte risposte qui. Il codice mostra come possiamo creare un'eccezione, come possiamo usare quell'eccezione nei nostri metodi e, infine, come è possibile verificare in un unit test, sollevando le giuste eccezioni.

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()

3

Puoi usare assertRaises dal modulo 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)

-5

Mentre tutte le risposte sono perfettamente soddisfacenti, stavo cercando un modo per testare se una funzione ha sollevato un'eccezione senza fare affidamento su framework di unit test e dover scrivere classi di test.

Ho finito per scrivere quanto segue:

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 non riesce sulla linea giusta:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.