web-dev-qa-db-fra.com

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, je voudrais 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 trouver comment le résoudre. Puis-je en quelque sorte monkeypatch la méthode assertRaises ()? Je préfère ne pas modifier toutes les lignes 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 puis-je tester l'argument dans une exception?

C'est ainsi que je le fais 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 __== '__main__':
    unittest.main()  

Pour vérifier le message d'erreur, je change simplement le type d'erreur dans assertRaises () en par exemple IOError. Ensuite, je peux 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)

Aucune suggestion?/Jonas

MODIFIER:

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

Je remplace simplement la méthode assertRaises (), comme vu ci-dessous. Ça marche comme du charme!

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 de la sortie résultante:

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
66
jonasberg

Out-of-the-box unittest ne fait pas cela. Si c'est quelque chose que vous voulez 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 vraiment, 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 lui, vous ne devriez pas inclure les messages en tant que littéraux de chaîne. Vous devez faire avec eux ce que vous faites avec toute autre chaîne importante: les définir comme constantes dans un module que vous importez et que quelqu'un est responsable de la relecture. Un développeur qui mal orthographie des mots dans son code les orthographiera également dans ses cas de test.

44
Robert Rossney

J'ai une fois préféré la réponse la plus excellente donnée ci-dessus par @Robert Rossney. De nos jours, je préfère utiliser assertRaises comme gestionnaire de contexte (une nouvelle fonctionnalité dans unittest2) comme ceci:

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

Vous recherchez assertRaisesRegexp , qui est disponible depuis Python 2.7. À partir des documents:

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

ou:

with self.assertRaisesRegexp(ValueError, 'literal'):
    int('XYZ')
50
Iodnas

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')
22
yalei du

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 Codacy . Le problème est qu'il ne sait pas que assertRaises peut être utilisé comme gestionnaire de contexte et qu'il signale que tous les arguments ne sont pas passés à la méthode assertRaises.

Donc, je voudrais 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 Python 3 (nous appelons e.args[0] car les erreurs dans Py3 n'ont pas d'attribut message).
5
oblalex