Comment testez-vous qu'une fonction Python lève une exception?
Comment écrire un unittest qui échoue seulement si une fonction ne lance pas une exception attendue?
11 réponses
Utiliser TestCase.assertRaises
(ou TestCase.failUnlessRaises
) à partir du module unittest, par exemple:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)
Depuis Python 2.7, vous pouvez utiliser le gestionnaire de contexte pour obtenir l'objet d'Exception réel lancé:
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
Dans Python 3.5, vous avez de l'envelopper context.exception
dans str
, sinon vous obtiendrez un TypeError
self.assertTrue('This is broken' in str(context.exception))
Le code de ma réponse précédente peut être simplifié en:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
Et si afunction prend des arguments, il suffit de les passer dans assertRaises comme ceci:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
Comment testez-vous qu'une fonction Python lève une exception?
Comment écrire un test qui échoue seulement si une fonction ne lance pas une exception attendue?
Réponse Courte:
Utilisez la méthode self.assertRaises
comme gestionnaire de contexte:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
Démonstration
L'approche des meilleures pratiques est assez facile à démontrer dans un shell Python.
Le unittest
bibliothèque
En python 2.7 ou 3:
import unittest
En python 2.6, vous pouvez installer un backport de la bibliothèque unittest
de 2.7, appelée unittest2 , et simplement l'alias unittest
:
import unittest2 as unittest
Exemple de tests
Maintenant, collez dans votre shell Python le test suivant de la sécurité de type de 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 One utilise assertRaises
en tant que gestionnaire de contexte, ce qui garantit que l'erreur est correctement détectée et nettoyée, lors de l'enregistrement.
On peut également l'écrire sans, le gestionnaire de contexte, voir test de deux. Le premier argument serait le type d'erreur que vous prévoyez d'augmenter, le deuxième argument, la fonction que vous testez, et les args restants et les args de mots clés seront passés à cette fonction.
Je pense qu'il est beaucoup plus simple, lisible et maintenable d'utiliser simplement le gestionnaire de contexte.
Exécution des tests
Pour exécuter les tests:
unittest.main(exit=False)
Dans Python 2.6, vous aurez probablement besoin de :
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Et votre terminal devrait afficher ce qui suit:
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s
OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>
Et nous voyons cela comme nous l'attendons, en essayant d'ajouter un 1
et un '1'
résultat dans un TypeError
.
Pour une sortie plus détaillée, essayez ceci:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Votre code devrait suivre ce modèle (ceci est un test de style de module unittest):
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')
Sur Python assertRaises vérifie uniquement si une exception a été déclenchée.
À Partir de: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/
Tout d'abord, voici la fonction correspondante (toujours dum :p) dans le fichier 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
Voici le test à effectuer (seul ce test est inséré):
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()
Nous sommes maintenant prêts à tester notre fonction! Voici ce qui se passe lorsque vous essayez d'exécuter le 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)
Le TypeError est actullay déclenché et génère un échec de test. Le problème, c'est que c'est exactement le comportement que nous voulions :s.
Pour éviter cette erreur, exécutez simplement la fonction en utilisant lambda dans l'appel de test:
self.assertRaises(TypeError, lambda: df.square_value(self.false_int))
La sortie finale:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Parfait !
... et pour moi, c'est parfait aussi!!
Merci beaucoup M. Julien Lengrand-Lambert
J'utilise doctest[1] presque partout, parce que j'aime le fait de me documenter et tester mes fonctions en même temps.
Jetez un oeil à ce code:
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()
Si vous mettez cet exemple dans un module et l'exécutez à partir de la ligne de commande, les deux cas de test sont évalués et vérifiés.
[1] la documentation Python: 23.2 doctest -- Test interactif de Python exemples
Jetez un oeil à la méthodeassertRaises du module unittest
.
Je viens de découvrir que la bibliothèque Mock fournit une méthode assertRaisesWithMessage () (dans son unittest.Sous-classe TestCase), qui vérifiera non seulement que l'exception attendue est déclenchée, mais aussi qu'elle est déclenchée avec le message attendu:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
'expected message',
mymod.myfunc)
, Vous pouvez construire votre propre contextmanager
pour vérifier si l'exception a été soulevée.
import contextlib
@contextlib.contextmanager
def raises(exception):
try:
yield
except exception as e:
assert True
else:
assert False
, puis vous pouvez utiliser raises
comme ceci:
with raises(Exception):
print "Hola" # Calls assert False
with raises(Exception):
raise Exception # Calls assert True
Si vous utilisez pytest
, Cette chose est déjà implémentée. Vous pouvez faire pytest.raises(Exception)
:
Exemple:
def test_div_zero():
with pytest.raises(ZeroDivisionError):
1/0
Et le résultat:
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
Vous pouvez utiliser assertRaises à partir du module 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)