2011-12-29 12 views
46

Per assicurarsi che i messaggi di errore dal mio modulo siano informativi, vorrei vedere tutti i messaggi di errore rilevati da assertRaises(). Oggi lo faccio per ogni assertRaises(), ma poiché ce ne sono molti nel codice di test diventa molto noioso.Come mostrare i messaggi di errore rilevati da assertRaises() in unittest in Python2.7?

Come posso stampare i messaggi di errore per tutte le assertRaises()? Ho studiato la documentazione su http://docs.python.org/library/unittest.html senza capire come risolverlo. Posso in qualche modo monkeypatch il metodo assertRaises()? Preferisco non modificare tutte le righe assertRaises() nel codice di test, poiché il più delle volte utilizzo il codice di prova nel modo standard.

Credo che questa domanda è legata alla Python unittest: how do I test the argument in an Exceptions?

Questo è come lo faccio oggi. Per esempio:

#!/usr/bin/env python 

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

E il codice di prova:

#!/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() 

Per controllare il messaggio di errore, ho semplicemente cambiare il tipo di errore nelle assertRaises() per, ad esempio IOError. Quindi posso vedere il messaggio di errore:

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) 

Qualche suggerimento? /Jonas

EDIT:

Con i suggerimenti di Robert Rossney sono riuscito a risolvere il problema. Non è principalmente inteso per errori di ortografia, ma per assicurarsi che i messaggi di errore siano veramente significativi per l'utente del modulo. La normale funzionalità di unittest (questo è il modo in cui la utilizzo la maggior parte delle volte) si ottiene impostando SHOW_ERROR_MESSAGES = False.

Semplicemente sovrascrivo il metodo assertRaises(), come illustrato di seguito. Funziona come un incantesimo!

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]) 

Una frazione del output risultante:

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 
+4

Perché continuare a utilizzare assertRaises se è necessario controllare gli argomenti? Perché non catturare semplicemente l'eccezione ed esaminarla usando 'try' e' except'? –

risposta

34

Out-of-the-box unittest non fa questo. Se questo è qualcosa che si vuole fare di frequente, si può provare qualcosa di simile:

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) 

derivare la classi di unit test da ExtendedTestCase invece di unittest.TestCase.

Ma in realtà, se si è semplicemente preoccupati dei messaggi di errore con errori di ortografia e si è preoccupati di voler creare casi di test attorno ad esso, non si dovrebbero inserire i messaggi come stringhe letterali. Dovresti fare con loro quello che fai con altre stringhe importanti: definirle come costanti in un modulo che importi e che qualcuno è responsabile della correzione di bozze. Uno sviluppatore che digita male le parole nel suo codice le farà anche errori di ortografia nei suoi casi di test.

+11

+1 per "Uno sviluppatore che digita male le parole nel suo codice le farà anche errori di ortografia nei suoi casi di test." – Johnsyweb

+7

Per me, è molto più eclatante quando si esegue il test per verificare che si verifichi un errore specifico, ma un test può "passare" a causa di effetti collaterali indesiderati. Per esempio. l'errore che ci si aspettava non venisse generato, ma lo stesso tipo di errore è stato sollevato altrove, soddisfacendo così il test. Il test passa, il codice è bug. Lo stesso accordo per gli errori che sottoclassi l'errore che stai cercando - se il tuo test è troppo generico, finisci per cogliere qualcosa che non ti aspetti. –

+1

Si dovrebbe usare '' inst.args [0] '' invece di '' inst.message'' Per eseguire questo codice sia su Python 2 e Python 3 – oblalex

71

Una volta ho preferito la risposta più eccellente di cui sopra @Robert Rossney.Al giorno d'oggi, io preferisco usare assertRaises come manager contesto (una nuova funzionalità in unittest2) in questo modo:

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

NB. assertRaises può essere usato come gestore di contesto da 'unittest' in Python 2.7. Funzionalità di backport unittest2 per le versioni precedenti di Python. http://docs.python.org/2/library/unittest.html#unittest.TestCase.assertRaises – powlo

+0

Cosa succede se, il codice fallisce nella parte "with" .... nel mio caso, che con la parte fallisce ... .so voglio mostrare un messaggio .. come possiamo fare per altre semplici affermazioni, per esempio self.assertEqual (cm.exception.faultCode, 101001, 'Il codice di errore non corrisponde al codice di errore previsto% d'% 101001) –

+0

@arindamroychowdhury, Mi dispiace, ma non ho codificato alcun Python da un po 'di tempo, quindi non conosco la risposta alla tua domanda. Buona fortuna. Forse, una delle altre persone qui potrebbe rispondere meglio alla tua domanda. In bocca al lupo. – mkelley33

34

Siete alla ricerca di assertRaisesRegexp, che è disponibile dal Python 2.7. Dalla documentazione:

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

o:

with self.assertRaisesRegexp(ValueError, 'literal'): 
    int('XYZ') 
+0

sì, ma se l'errore previsto non è aumentato, non vedrai mai il messaggio/non può cambiare quello predefinito. Estremamente fastidioso quando si testano alcuni parametri in loop - non si conosce per quale parametro la funzione passi senza errori previsti. – Mesco

4

mkelley33 dà risposta bello, ma questo approccio può essere rilevato come problema da parte di alcuni strumenti di analisi del codice come Codacy. Il problema è che non sa che assertRaises può essere utilizzato come gestore di contesto e segnala che non tutti gli argomenti vengono passati a assertRaisesmetodo.

Così, mi piacerebbe migliorare la risposta Rossney di Robert:

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__)) 

Le differenze principali sono:

  1. proviamo tipo di eccezione.
  2. Possiamo eseguire questo codice sia su Python 2 che su Python 3 (chiamiamo e.args[0] perché gli errori in Py3 non hanno l'attributo message).
8

Se si desidera che il messaggio di errore corrisponde esattamente qualcosa:

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