109 votes

Comment afficher les messages d'erreur capturés par assertRaises() dans unittest en Python2.7 ?

Afin de m'assurer que les messages d'erreur de mon module sont informatifs, j'aimerais voir tous les messages d'erreur capturés par assertRaises(). Aujourd'hui, je le fais pour chaque assertRaises(), mais comme il y en a beaucoup dans le code de test, cela devient très fastidieux.

Comment puis-je imprimer les messages d'erreur pour tous les assertRaises() ? J'ai étudié la documentation sur http://docs.python.org/library/unittest.html sans savoir comment le résoudre. Puis-je d'une manière ou d'une autre monkeypater la méthode assertRaises() ? Je préfère ne pas changer toutes les lignes de assertRaises() dans le code de test, car j'utilise le plus souvent le code de test de manière standard.

Je suppose que cette question est liée à Python unittest : comment tester l'argument dans une Exceptions ?

Voici comment je procède aujourd'hui. Par exemple :

#!/usr/bin/env python

def fail():
    raise ValueError('Misspellled errrorr messageee')

Et le code de test :

#!/usr/bin/env python
import unittest
import failure   

class TestFailureModule(unittest.TestCase):

    def testFail(self):
        self.assertRaises(ValueError, failure.fail)

if __name__ == '__main__':
    unittest.main()  

Pour vérifier le message d'erreur, je change simplement le type d'erreur dans la fonction assertRaises() en IOError, par exemple. Je peux alors voir le message d'erreur :

 E
======================================================================
ERROR: testFail (__main__.TestFailureModule)
----------------------------------------------------------------------
Traceback (most recent call last):
 File "test_failure.py", line 8, in testFail
   self.assertRaises(IOError, failure.fail)
  File "/usr/lib/python2.7/unittest/case.py", line 471, in assertRaises
    callableObj(*args, **kwargs)
 File "/home/jonas/Skrivbord/failure.py", line 4, in fail
    raise ValueError('Misspellled errrorr messageee')
ValueError: Misspellled errrorr messageee

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

Des suggestions ? /Jonas

EDITAR:

Grâce aux conseils de Robert Rossney, j'ai réussi à résoudre le problème. Il ne s'agit pas principalement de corriger les fautes d'orthographe, mais de s'assurer que les messages d'erreur sont vraiment significatifs pour l'utilisateur du module. La fonctionnalité normale d'unittest (c'est ainsi que je l'utilise la plupart du temps) est obtenue en mettant SHOW_ERROR_MESSAGES = False.

Je surcharge simplement la méthode assertRaises(), comme indiqué ci-dessous. Cela fonctionne à merveille !

SHOW_ERROR_MESSAGES = True

class NonexistantError(Exception):
    pass

class ExtendedTestCase(unittest.TestCase):
    def assertRaises(self, excClass, callableObj, *args, **kwargs):
        if SHOW_ERROR_MESSAGES:
            excClass = NonexistantError
        try:
            unittest.TestCase.assertRaises(self, excClass, callableObj, *args, **kwargs)
        except:
            print '\n    ' + repr(sys.exc_info()[1]) 

Une fraction du résultat obtenu :

testNotIntegerInput (__main__.TestCheckRegisteraddress) ... 
    TypeError('The registeraddress must be an integer. Given: 1.0',)

    TypeError("The registeraddress must be an integer. Given: '1'",)

    TypeError('The registeraddress must be an integer. Given: [1]',)

    TypeError('The registeraddress must be an integer. Given: None',)
ok
testCorrectNumberOfBytes (__main__.TestCheckResponseNumberOfBytes) ... ok
testInconsistentLimits (__main__.TestCheckNumerical) ... 
    ValueError('The maxvalue must not be smaller than minvalue. Given: 45 and 47, respectively.',)

    ValueError('The maxvalue must not be smaller than minvalue. Given: 45.0 and 47.0, respectively.',)
ok
testWrongValues (__main__.TestCheckRegisteraddress) ... 
    ValueError('The registeraddress is too small: -1, but minimum value is 0.',)

    ValueError('The registeraddress is too large: 65536, but maximum value is 65535.',)
ok
testTooShortString (__main__.TestCheckResponseWriteData) ... 
    ValueError("The payload is too short: 2, but minimum value is 4. Given: '\\x00X'",)

    ValueError("The payload is too short: 0, but minimum value is 4. Given: ''",)

    ValueError("The writedata is too short: 1, but minimum value is 2. Given: 'X'",)

    ValueError("The writedata is too short: 0, but minimum value is 2. Given: ''",)
ok
testKnownValues (__main__.TestCreateBitPattern) ... ok
testNotIntegerInput (__main__.TestCheckSlaveaddress) ... 
    TypeError('The slaveaddress must be an integer. Given: 1.0',)

    TypeError("The slaveaddress must be an integer. Given: '1'",)

    TypeError('The slaveaddress must be an integer. Given: [1]',)

    TypeError('The slaveaddress must be an integer. Given: None',)
ok

170voto

mkelley33 Points 1691

J'ai déjà préféré l'excellente réponse donnée ci-dessus par @Robert Rossney. Aujourd'hui, je préfère utiliser assertRaises comme gestionnaire de contexte (une nouvelle capacité dans unittest2) comme suit :

with self.assertRaises(TypeError) as cm:
    failure.fail()
self.assertEqual(
    'The registeraddress must be an integer. Given: 1.0',
    str(cm.exception)
)

77voto

Iodnas Points 575

Vous recherchez assertRaisesRegex qui est disponible depuis Python 3.2. D'après la documentation :

self.assertRaisesRegex(ValueError, "invalid literal for.*XYZ'$",
                       int, 'XYZ')

ou :

with self.assertRaisesRegex(ValueError, 'literal'):
    int('XYZ')

PS : si vous utilisez Python 2.7, le nom correct de la méthode est assertRaisesRegexp .

62voto

Robert Rossney Points 43767

Prêt à l'emploi unittest ne le fait pas. Si vous voulez le faire fréquemment, vous pouvez essayer quelque chose comme ceci :

class ExtendedTestCase(unittest.TestCase):

  def assertRaisesWithMessage(self, msg, func, *args, **kwargs):
    try:
      func(*args, **kwargs)
      self.assertFail()
    except Exception as inst:
      self.assertEqual(inst.message, msg)

Dérivez vos classes de tests unitaires de ExtendedTestCase au lieu de unittest.TestCase .

Mais en réalité, si vous êtes simplement préoccupé par les messages d'erreur mal orthographiés, et suffisamment préoccupé pour vouloir construire des cas de test autour de cela, vous ne devriez pas intégrer les messages en tant que chaînes de caractères littérales. Vous devriez faire avec eux ce que vous faites avec toutes les autres chaînes importantes : les définir comme des constantes dans un module que vous importez et que quelqu'un est chargé de relire. Un développeur qui fait des fautes d'orthographe dans son code en fera également dans ses cas de test.

43voto

yalei du Points 587

Si vous voulez que le message d'erreur corresponde exactement à quelque chose :

with self.assertRaises(ValueError) as error:
  do_something()
self.assertEqual(error.exception.message, 'error message')

7voto

oblalex Points 221

Mkelley33 donne une bonne réponse, mais cette approche peut être détectée comme un problème par certains outils d'analyse de code comme La codification . Le problème est qu'il ne sait pas que assertRaises peut être utilisé comme gestionnaire de contexte et signale que tous les arguments ne sont pas transmis à assertRaises méthode .

J'aimerais donc améliorer la réponse de Robert Rossney :

class TestCaseMixin(object):

    def assertRaisesWithMessage(self, exception_type, message, func, *args, **kwargs):
        try:
            func(*args, **kwargs)
        except exception_type as e:
            self.assertEqual(e.args[0], message)
        else:
            self.fail('"{0}" was expected to throw "{1}" exception'
                      .format(func.__name__, exception_type.__name__))

Les principales différences sont les suivantes :

  1. Nous testons le type d'exception.
  2. Nous pouvons exécuter ce code à la fois sur Python 2 et sur Python 3 (nous appelons e.args[0] parce que les erreurs dans Py3 n'ont pas de message ).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X