Comment écrire un unittest qui échoue uniquement si une fonction ne lève pas d'exception attendue?
Comment écrire un unittest qui échoue uniquement si une fonction ne lève pas d'exception attendue?
Réponses:
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)
myfunc
vous devez les ajouter en tant qu'arguments à l'appel assertRaises. Voir la réponse de Daryl Spitzer.
self.assertRaises(TypeError, mymod.myfunc)
. Vous pouvez trouver une liste complète des exceptions intégrées
self.assertRaises(SomeCoolException, Constructor, arg1)
Depuis Python 2.7, vous pouvez utiliser le gestionnaire de contexte pour obtenir l'objet réel de l'exception levée:
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
En Python 3.5 , vous devez envelopper context.exception
dans str
, sinon vous aurez unTypeError
self.assertTrue('This is broken' in str(context.exception))
context.exception
ne donne pas le message; c'est un type.
import unittest2
, vous devez utiliser str()
, c'est-à-dire self.assertTrue('This is broken' in str(context.exception))
.
Le code dans ma réponse précédente peut être simplifié pour:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
Et si une fonction prend des arguments, passez-les simplement dans assertRaises comme ceci:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
2.7.15
. Si afunction
dans self.assertRaises(ExpectedException, afunction, arg1, arg2)
est l'initialiseur de classe, vous devez passer self
comme premier argument, par exemple, self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Comment testez-vous qu'une fonction Python lève une exception?
Comment écrire un test qui échoue uniquement si une fonction ne lève pas d'exception attendue?
Utilisez la self.assertRaises
méthode comme gestionnaire de contexte:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
L'approche des meilleures pratiques 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 backport de la unittest
bibliothèque de 2.7 , appelé unittest2 , et juste alias cela comme unittest
:
import unittest2 as unittest
Maintenant, collez dans votre shell Python le test suivant de 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 test que l'on utilise assertRaises
comme 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 test deux. Le premier argument serait le type d'erreur que vous vous attendez à soulever, le deuxième argument, la fonction que vous testez, et les arguments et les arguments de mot-clé restants seront passés à cette fonction.
Je pense qu'il est beaucoup plus simple, lisible et maintenable de simplement utiliser le gestionnaire de contexte.
Pour exécuter les tests:
unittest.main(exit=False)
Dans Python 2.6, vous aurez probablement besoin des éléments suivants :
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Et votre terminal devrait produire les informations suivantes:
..
----------------------------------------------------------------------
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 doit suivre ce modèle (il s'agit d'un test de style de module le plus simple):
def test_afunction_throws_exception(self):
try:
afunction()
except ExpectedException:
pass
except Exception:
self.fail('unexpected exception raised')
else:
self.fail('ExpectedException not raised')
Sur Python <2.7, cette construction est utile pour vérifier des valeurs spécifiques dans l'exception attendue. La fonction unittest assertRaises
vérifie uniquement si une exception a été déclenchée.
assertRaises
vous, vous obtiendrez une ERREUR au lieu d'un ÉCHEC.
sur: 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)
L'erreur TypeError est déclenchée et génère un échec de test. Le problème est que c'est exactement le comportement que nous voulions: le par.
Pour éviter cette erreur, exécutez simplement la fonction à l'aide de 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
Cette affirmation de test renvoie en fait un faux positif . Cela se produit parce que le lambda à l'intérieur des 'assertRaises' est l'unité qui déclenche l'erreur de type et non la fonction testée.
self.assertRaises(TypeError, df.square_value(self.false_int))
appelle la méthode et renvoie le résultat. Ce que vous voulez, c'est passer la méthode et tous les arguments et laisser les plus instants l'appeler:self.assertRaises(TypeError, df.square_value, self.false_int)
Vous pouvez créer le vôtre 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
Et 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
unittest
module!
J'utilise doctest [1] presque partout parce que j'aime le fait que je documente et teste mes fonctions en même temps.
Jetez un œil à 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 placez 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] Documentation Python: 23.2 doctest - Test d'exemples interactifs Python
Je viens de découvrir que la bibliothèque Mock 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)
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 dans un test unitaire, les exceptions correctes étant levé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()
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)
Comme je n'ai vu aucune explication détaillée sur la façon de vérifier si nous avons obtenu une exception spécifique parmi une liste acceptée à l'aide du gestionnaire de contexte, ou d'autres détails d'exception, j'ajouterai le mien (vérifié sur python 3.8).
Si je veux juste vérifier que la fonction augmente par exemple TypeError
, j'écrirais:
with self.assertRaises(TypeError):
function_raising_some_exception(parameters)
Si je veux vérifier que la fonction soulève l'un TypeError
ou l' autre IndexError
, j'écrirais: avec self.assertRaises ((TypeError, IndexError)): function_raising_some_exception (parameters)
Et si je veux encore plus de détails sur l'exception levée, je pourrais l'attraper dans un contexte comme celui-ci:
# Here I catch any exception
with self.assertRaises(Exception) as e:
function_raising_some_exception(parameters)
# Here I check actual eception type (but I could check anything else about that specific exception, like it's actual message or values stored in the exception)
self.assertTrue(type(e.exception) in [TypeError,MatrixIsSingular])
Bien que toutes les réponses soient parfaitement correctes, je cherchais un moyen de tester si une fonction levait une exception sans s'appuyer sur des frameworks de tests unitaires et sans avoir à écrire des classes de test.
J'ai fini par écrire ce qui suit:
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)
Et cela échoue sur la bonne ligne:
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