Wie werden die von assertRaises () in unittest in Python2.7 abgefangenen Fehlermeldungen angezeigt?

77

Um sicherzustellen, dass die Fehlermeldungen von meinem Modul informativ sind, möchte ich alle von assertRaises () abgefangenen Fehlermeldungen sehen. Heute mache ich es für jede assertRaises (), aber da es viele davon im Testcode gibt, wird es sehr langweilig.

Wie kann ich die Fehlermeldungen für alle assertRaises () drucken? Ich habe die Dokumentation unter http://docs.python.org/library/unittest.html studiert, ohne herauszufinden, wie ich sie lösen kann. Kann ich die assertRaises () -Methode irgendwie monkeypatchen? Ich ziehe es vor, nicht alle assertRaises () -Zeilen im Testcode zu ändern, da ich den Testcode meistens standardmäßig verwende.

Ich denke, diese Frage bezieht sich auf Python unittest: Wie teste ich das Argument in einer Ausnahme?

So mache ich es heute. Zum Beispiel:

#!/usr/bin/env python

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

Und der Testcode:

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

Um die Fehlermeldung zu überprüfen, ändere ich einfach den Fehlertyp in assertRaises () in beispielsweise IOError. Dann kann ich die Fehlermeldung sehen:

 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)

Irgendwelche Vorschläge? / Jonas

BEARBEITEN:

Mit den Hinweisen von Robert Rossney gelang es mir, das Problem zu lösen. Es ist nicht hauptsächlich für Rechtschreibfehler gedacht, sondern um sicherzustellen, dass die Fehlermeldungen für den Benutzer des Moduls wirklich aussagekräftig sind. Die normale Funktionalität von unittest (so verwende ich es meistens) wird erreicht, indem SHOW_ERROR_MESSAGES = False gesetzt wird.

Ich überschreibe einfach die assertRaises () -Methode, wie unten gezeigt. Es funktioniert wie 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]) 

Ein Bruchteil der resultierenden Ausgabe:

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
Jonasberg
quelle
4
Warum weiterhin assertRaises verwenden, wenn Sie die Argumente überprüfen müssen? Warum nicht einfach die Ausnahme abfangen und mit try und untersuchen except?
S.Lott

Antworten:

53

Out-of-the-Box unittestmacht das nicht. Wenn Sie dies häufig tun möchten, können Sie Folgendes ausprobieren:

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)

Leiten Sie Ihre Unit-Test-Klassen von ExtendedTestCasestatt unittest.TestCase.

Aber wirklich, wenn Sie nur über falsch geschriebene Fehlermeldungen besorgt sind und genug Bedenken haben, um Testfälle darum herum zu erstellen, sollten Sie Nachrichten nicht als Zeichenfolgenliterale einbinden. Sie sollten mit ihnen das tun, was Sie mit anderen wichtigen Zeichenfolgen tun: Definieren Sie sie als Konstanten in einem Modul, das Sie importieren, und dass jemand für das Korrekturlesen verantwortlich ist. Ein Entwickler, der Wörter in seinem Code falsch schreibt, schreibt sie auch in seinen Testfällen falsch.

Robert Rossney
quelle
21
+1 für "Ein Entwickler, der Wörter in seinem Code falsch schreibt, schreibt sie auch in seinen Testfällen falsch."
Johnsyweb
17
Für mich ist es weitaus ungeheuerlicher, wenn Sie testen, ob ein bestimmter Fehler auftritt, aber ein Test kann aufgrund unbeabsichtigter Nebenwirkungen „bestanden“ werden. ZB wurde der erwartete Fehler nicht ausgelöst, aber der gleiche Fehlertyp wird an anderer Stelle ausgelöst, wodurch der Test erfüllt wird. Der Test besteht, der Code ist fehlerhaft. Gleiches gilt für Fehler, die den gesuchten Fehler unterordnen. Wenn Ihr Test zu allgemein ist, erhalten Sie am Ende etwas, das Sie nicht erwarten.
Mark Simpson
1
Sie sollten inst.args[0] stattdessen verwenden inst.message , um diesen Code sowohl auf Python 2 als auch auf Python 3
auszuführen
3
Dies ist nicht wahr, unittest macht dies sofort.
Jonathan Hartley
Neato Beispiel für Unterklassen. Es fällt mir jedoch schwer zu glauben, dass dies notwendig oder vorteilhaft ist, da unittestdie sofort einsatzbereiten Funktionen sehr umfangreich sind.
Tom Russell
121

Ich habe einmal die beste Antwort von @Robert Rossney vorgezogen. Heutzutage bevorzuge ich die Verwendung von assertRaises als Kontextmanager (eine neue Funktion in unittest2) wie folgt:

with self.assertRaises(TypeError) as cm:
    failure.fail()
self.assertEqual(
    'The registeraddress must be an integer. Given: 1.0',
    str(cm.exception)
)
mkelley33
quelle
2
NB. assertRaises kann als Kontextmanager von 'unittest' in Python 2.7 verwendet werden. unittest2-Backports-Funktionen für frühere Versionen von Python. docs.python.org/2/library/…
powlo
Was ist, wenn der Code am "with" -Teil fehlschlägt? In meinem Fall schlägt der Code mit dem Teil fehl. Ich möchte also eine Nachricht anzeigen. Wie wir es für andere einfache Asserts tun können, z. B. self.assertEqual (cm .exception.faultCode, 101001, 'Fehlercode stimmt nicht mit erwartetem Fehlercode
überein
@arindamroychowdhury, es tut mir leid, aber ich habe seit einiger Zeit kein Python mehr codiert, daher kenne ich die Antwort auf Ihre Frage nicht. Viel Glück. Vielleicht könnte einer der anderen Leute hier Ihre Frage besser beantworten. Viel Glück.
mkelley33
Ich benutze Python 2.7. Ich musste str (cm.exception) durch cm.exception.parameter ersetzen.
Chuck
58

Sie suchen nach assertRaisesRegex , das seit Python 3.2 verfügbar ist. Aus den Dokumenten:

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

oder:

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

PS: Wenn Sie Python 2.7 verwenden, lautet der korrekte Methodenname assertRaisesRegexp.

Iodnas
quelle
Ja, aber wenn der erwartete Fehler nicht aufgetreten ist, wird die Meldung nie angezeigt / die Standardmeldung kann nicht geändert werden. Extrem unangenehm beim Testen einiger Parameter in einer Schleife - Sie wissen nicht, für welchen Parameter die Funktion ohne erwarteten Fehler durchläuft.
Mesco
2
Auf Python 3.6 heißt es DeprecationWarning: Please use assertRaisesRegex insteadbei Verwendungwith self.assertRaisesRegexp( RuntimeError, '...regex...' )
Benutzer
33

Wenn Sie möchten, dass die Fehlermeldung genau mit etwas übereinstimmt:

with self.assertRaises(ValueError) as error:
  do_something()
self.assertEqual(error.exception.message, 'error message')
yalei du
quelle
6
Ich musste str(error.exception)anstelle von error.exception.messageas error.exceptionkein messageAttribut in meinem Fall verwenden.
Rik Schoonbeek
6

mkelley33 gibt eine gute Antwort, aber dieser Ansatz kann von einigen Code-Analyse-Tools wie Codacy als Problem erkannt werden . Das Problem ist, dass es nicht weiß, assertRaisesdass es als Kontextmanager verwendet werden kann, und dass nicht alle Argumente an die assertRaises Methode übergeben werden .

Also möchte ich Roberts Rossney-Antwort verbessern:

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

Hauptunterschiede sind:

  1. Wir testen die Art der Ausnahme.
  2. Wir können diesen Code sowohl auf Python 2 als auch auf Python 3 e.args[0]ausführen (wir rufen auf, weil Fehler in Py3 kein messageAttribut haben).
Oblalex
quelle
Dies ist eine sehr elegante Lösung IMO. Falls Sie nicht mögen e.args[0], können Sie auch aufrufen str(e).
Laryx Decidua