Question

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

Était-ce utile?

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()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top