web-dev-qa-db-fra.com

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

Comment écrire un unittest qui échoue uniquement si une fonction ne lève pas l'exception attendue?

669
Daryl Spitzer

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)
_
575
Moe

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

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 __== '__main__':
    unittest.main()

http://docs.python.org/dev/library/unittest.html#unittest.TestCase.assertRaises


Dans Python 3.5 , vous devez envelopper context.exception dans str, sinon vous obtiendrez un TypeError

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

Le code dans ma réponse précédente peut être simplifié à:

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)
275
Daryl Spitzer

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?

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'

Manifestation

La meilleure approche pratique est assez facile à démontrer dans un shell Python.

La bibliothèque unittest

Dans Python 2.7 ou 3:

import unittest

Dans Python 2.6, vous pouvez installer un backport de la bibliothèque unittest de 2.7, appelée nittest2 , et simplement alias portant le nom unittest:

import unittest2 as unittest

Exemples de tests

Maintenant, collez dans votre Python Shell 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 assertRaises comme gestionnaire de contexte, ce qui garantit que l'erreur est correctement détectée et nettoyée, tout en étant enregistrée.

Nous pourrions aussi 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 second, à la fonction que vous testez, et les arguments restants et les arguments de mot clé seront transmis à cette fonction.

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

Lancer les 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 devrait afficher 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 nous y attendons, essayer d’ajouter un 1 et un '1' donne un TypeError.


Pour une sortie plus détaillée, essayez ceci:

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

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.

47
Daryl Spitzer

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

Premièrement, 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 __== "__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

16
macm

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

Et 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
11
Pigueiras

J'utilise doctest [1] presque partout parce que j'aime bien documenter et tester mes fonctions en même temps.

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 __== '__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 cas de test sont évalués et vérifiés.

[1] Documentation Python: 23.2 doctest - Exemples de test Python interactifs

10
pi.

Regardez la méthode assertRaises du module unittest.

9
Greg Hewgill

Je viens de découvrir que la Mock library fournit une méthode assertRaisesWithMessage () (dans sa sous-classe unittest.TestCase), qui vérifie non seulement que l'exception attendue est déclenchée, mais également qu'elle l'est également. message:

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)
6
Daryl Spitzer

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)
2
Bruno Carvalho

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 __== '__main__':
    unittest.main()
0