Come si fa a scrivere un unestest che fallisce solo se una funzione non genera un'eccezione prevista?
Come si fa a scrivere un unestest che fallisce solo se una funzione non genera un'eccezione prevista?
Risposte:
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)
myfunc
è necessario aggiungerli come argomenti alla chiamata assertRaises. Vedi la risposta di Daryl Spitzer.
self.assertRaises(TypeError, mymod.myfunc)
. Puoi trovare un elenco completo delle Eccezioni integrate qui: docs.python.org/3/library/exceptions.html#bltin-exceptions
self.assertRaises(SomeCoolException, Constructor, arg1)
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.exception
in str
, altrimenti si otterrà unTypeError
self.assertTrue('This is broken' in str(context.exception))
context.exception
non dà il messaggio; è un tipo.
import unittest2
, devi usare str()
, cioè self.assertTrue('This is broken' in str(context.exception))
.
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)
2.7.15
. Se afunction
in self.assertRaises(ExpectedException, afunction, arg1, arg2)
è l'inizializzatore di classe, è necessario passare self
come primo argomento, ad es. self.assertRaises(ExpectedException, Class, self, arg1, arg2)
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?
Utilizzare il self.assertRaises
metodo come gestore di contesto:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
L'approccio delle migliori pratiche è abbastanza facile da dimostrare in una shell Python.
La unittest
biblioteca
In Python 2.7 o 3:
import unittest
In Python 2.6, puoi installare un backport della unittest
libreria 2.7 , chiamato unittest2 , e solo alias come unittest
:
import unittest2 as unittest
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 assertRaises
come 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.
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 1
un '1'
risultato in a TypeError
.
Per un output più dettagliato, prova questo:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
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 assertRaises
verifica solo se è stata sollevata un'eccezione.
assertRaises
si otterrà un ERRORE anziché un GUASTO.
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.
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)
Puoi crearne uno tuo contextmanager
per 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 raises
questo 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
unittest
modulo!
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
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)
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()
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)
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