Comment testez-vous qu'une fonction Python génère une exception?
-
02-07-2019 - |
Question
Comment écrire un unittest qui échoue uniquement si une fonction ne lève pas l'exception attendue?
La solution
Utilisez 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)
Autres conseils
Depuis Python 2.7, vous pouvez utiliser le gestionnaire de contexte pour capturer l'objet Exception réel:
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 devez insérer 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é comme suit:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
Et si une fonction prend des arguments, il suffit de les transmettre à 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 uniquement si une fonction ne lance pas une exception attendue?
Réponse courte:
Utilisez la méthode self.assertRaises
en tant que gestionnaire de contexte:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
Démonstration
La meilleure approche pratique est assez facile à démontrer dans un shell Python.
La unittest
bibliothèque
En Python 2.7 ou 3:
import unittest
Dans Python 2.6, vous pouvez installer un portage de la bibliothèque assertRaises
de la 2.7, appelée unittest2 , et juste alias que comme 1
:
import unittest2 as unittest
Exemples 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')
Le premier test utilise '1'
en tant que gestionnaire de contexte, ce qui garantit que l'erreur est correctement détectée et nettoyée pendant son enregistrement.
Nous pourrions également l'écrire sans le gestionnaire de contexte, voir le test deux. Le premier argument correspond au type d'erreur que vous souhaitez générer, le deuxième argument, à la fonction que vous testez, et les arguments restants et les arguments de mot clé seront transmis à cette fonction.
Je pense que l'utilisation du gestionnaire de contexte est beaucoup plus simple, lisible et maintenable.
Exécution des tests
Pour exécuter les tests:
unittest.main(exit=False)
Dans Python 2.6, vous aurez probablement besoin de ce qui suit :
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Et votre terminal doit générer les éléments suivants:
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s
OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>
Et nous voyons cela comme nous nous attendions, en essayant d'ajouter un TypeError
et un <=> résultat dans un <=>.
Pour une sortie plus détaillée, essayez ceci:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Votre code doit suivre ce modèle (il s'agit d'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 < 2.7 cette construction est utile pour rechercher des valeurs spécifiques dans l'exception attendue. La fonction unittest assertRaises
vérifie uniquement si une exception a été déclenchée.
à partir de: http://www.lengrand.fr/2011 / 12 / pythonunittest-assertraises-raise-error /
Tout d'abord, voici la fonction (toujours dum: p) correspondante 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 de lancer 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)
L'erreur TypeError est actullay et génère un échec de test. Le problème 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))
Le résultat final:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Parfait!
... et pour moi, c'est parfait aussi!
Merci beaucoup à M. Julien Lengrand-Lambert
Vous pouvez créer votre propre contextmanager
pour vérifier si l'exception a été déclenchée.
import contextlib
@contextlib.contextmanager
def raises(exception):
try:
yield
except exception as e:
assert True
else:
assert False
Ensuite, 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
J'utilise doctest [1] un peu partout car j'apprécie le fait de documenter et de tester mes fonctions simultanément.
Regardez 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 que vous l'exécutez à partir de la ligne de commande, les deux scénarios de test sont évalués et vérifiés.
[1] Documentation Python: 23.2 doctest - Testez des exemples Python interactifs
Consultez la méthode assertRaises . le unittest
module.
Je viens de découvrir que la Mock library fournit une méthode assertRaisesWithMessage () () dans sa sous-classe unittest.TestCase), qui vérifiera non seulement que l'exception attendue est déclenchée, mais également 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 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)
Il y a beaucoup de réponses ici. Le code montre comment créer une exception, comment utiliser cette exception dans nos méthodes et, enfin, comment vérifier de manière unittest que les exceptions correctes sont déclenchées.
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()