Comment testez-vous qu'une fonction Python lève une exception?


Answers

Depuis Python 2.7, vous pouvez utiliser le gestionnaire de contexte pour obtenir l'objet réel Exception:

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 TypeError dans str , sinon vous obtiendrez un TypeError

self.assertTrue('This is broken' in str(context.exception))
Question

Comment écrire un unittest qui échoue seulement si une fonction ne lance pas une exception attendue?




Vous pouvez créer votre propre contextmanager de contextmanager pour vérifier si l'exception a été contextmanager .

import contextlib

@contextlib.contextmanager
def raises(exception):
    try:
        yield 
    except exception as e:
        assert True
    else:
        assert False

Et puis vous pouvez utiliser des 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



Jetez un oeil à la méthode assertRaises du module unittest .




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 tant que gestionnaire de contexte:

    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'

Manifestation

L'approche des meilleures pratiques est assez facile à démontrer dans un shell Python.

La bibliothèque unittest

En Python 2.7 ou 3:

import unittest

Dans Python 2.6, vous pouvez installer un backport de la librairie unittest de 2.7, appelée unittest2 , et juste un alias comme unittest :

import unittest2 as unittest

Exemples de tests

Maintenant, collez dans votre shell Python le test suivant de la sécurité de type 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 assertRaises comme gestionnaire de contexte, ce qui garantit que l'erreur est correctement détectée et nettoyée pendant l'enregistrement.

Nous pourrions aussi l'écrire sans le gestionnaire de contexte, voir le test deux. Le premier argument serait le type d'erreur que vous envisagez d'élever, le second argument, la fonction que vous testez, et les args et mots-clés restants seront passés à cette fonction.

Je pense qu'il est beaucoup plus simple, lisible et maintenable de simplement utiliser le gestionnaire de contexte.

Exécuter les tests

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 sortir ce qui suit:

..
----------------------------------------------------------------------
Ran 2 tests in 0.007s

OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>

Et nous voyons que nous attendons, en essayant d'ajouter un 1 et un '1' résultat dans un TypeError .

Pour une sortie plus verbeuse, essayez ceci:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))



de: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/

Tout d'abord, voici la fonction correspondante (still 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 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))

La sortie finale:

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Parfait!

... et pour moi c'est parfait aussi !!

Merci beaucoup M. Julien Lengrand-Lambert