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?

519
demandé sur Aaron Hall 2008-09-25 00:00:35

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)
451
répondu Moe 2016-05-05 15:59:38

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))
320
répondu Art 2018-08-20 06:26:18

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)
207
répondu Daryl Spitzer 2014-06-18 13:37:56

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))
95
répondu Aaron Hall 2017-05-23 12:10:43

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.

30
répondu Daryl Spitzer 2016-05-05 16:08:52

À 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

12
répondu macm 2013-02-05 17:04:37

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

8
répondu pi. 2008-09-25 11:17:18

Jetez un oeil à la méthodeassertRaises du module unittest.

7
répondu Greg Hewgill 2015-01-21 12:57:25

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)
5
répondu Daryl Spitzer 2008-10-28 00:13:39

, 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
5
répondu Pigueiras 2014-06-18 10:09:17

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)
1
répondu Bruno Carvalho 2010-10-21 00:12:46