Wie testest du, dass eine Python-Funktion eine Ausnahme auslöst?

Antworten:

679

Verwenden Sie TestCase.assertRaises(oder TestCase.failUnlessRaises) aus dem unittest-Modul, zum Beispiel:

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)
Moe
quelle
9
Gibt es eine Möglichkeit, das Gegenteil davon zu tun? Wie schlägt es nur fehl, wenn die Funktion die Ausnahme auslöst?
BUInvent
67
Beachten Sie, dass myfuncSie zum Übergeben von Argumenten diese als Argumente zum assertRaises-Aufruf hinzufügen müssen. Siehe Daryl Spitzers Antwort.
cbron
1
Gibt es eine Möglichkeit, mehrere Ausnahmetypen zuzulassen?
Dinesh
1
Sie können die integrierten Ausnahmen von Python verwenden, um die Behauptung schnell zu testen. Verwenden Sie zum Beispiel die Antwort von @ Moe oben : self.assertRaises(TypeError, mymod.myfunc). Eine vollständige Liste der integrierten Ausnahmen finden Sie hier: docs.python.org/3/library/exceptions.html#bltin-exceptions
Raymond Wachaga
3
Gleiches gilt für das Testen von Klassenkonstruktoren:self.assertRaises(SomeCoolException, Constructor, arg1)
Tschumann
476

Seit Python 2.7 können Sie den Kontextmanager verwenden, um das tatsächlich ausgelöste Exception-Objekt abzurufen:

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


In Python 3.5 , müssen Sie wickeln context.exceptionin str, sonst werden Sie eine bekommenTypeError

self.assertTrue('This is broken' in str(context.exception))
Kunst
quelle
6
Ich verwende Python 2.7.10 und das oben genannte funktioniert nicht. context.exceptiongibt die Nachricht nicht; Es ist ein Typ.
LateCoder
6
Auch in Python 2.7 (zumindest in meinem 2.7.6) import unittest2müssen Sie verwenden str(), dh self.assertTrue('This is broken' in str(context.exception)).
Sohail Si
4
Zwei Dinge: 1. Sie können assertIn anstelle von assertTrue verwenden. ZB self.assertIn ('Dies ist kaputt', context.exception) 2. In meinem Fall scheint context.exception unter Verwendung von 2.7.10 ein Array von Zeichen zu sein. Die Verwendung von str funktioniert nicht. Am Ende habe ich folgendes gemacht: '' .join (context.exception) Also, zusammengesetzt: self.assertIn ('Dies ist kaputt', '' .join (context.exception))
Blockcipher
1
Ist es normal, dass Ihre Methode die Testkonsole mit dem Traceback der Ausnahme verstopft? Wie verhindere ich das?
MadPhysicist
1
später habe ich einen anderen Weg gefunden, um die Nachricht als str der Ausnahme zu erhalten, es ist err = context.exception.message. Und dann können Sie auch self.assertEqual (err, 'This is break') verwenden, um den Test durchzuführen.
Zhihong
326

Der Code in meiner vorherigen Antwort kann vereinfacht werden zu:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction)

Und wenn eine Funktion Argumente benötigt, geben Sie sie einfach an assertRaises wie folgt weiter:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)
Daryl Spitzer
quelle
17
Der zweite Ausschnitt darüber, was zu tun ist, wenn ein Argument übergeben wird, war wirklich hilfreich.
Sabyasachi
Ich benutze 2.7.15. Wenn afunctionin self.assertRaises(ExpectedException, afunction, arg1, arg2)der Klasseninitialisierer ist, müssen Sie selfals erstes Argument übergeben, z. B. self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Minh Tran
128

Wie testest du, dass eine Python-Funktion eine Ausnahme auslöst?

Wie schreibt man einen Test, der nur fehlschlägt, wenn eine Funktion keine erwartete Ausnahme auslöst?

Kurze Antwort:

Verwenden Sie die self.assertRaisesMethode als Kontextmanager:

    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'

Demonstration

Der Best-Practice-Ansatz lässt sich in einer Python-Shell recht einfach demonstrieren.

Die unittestBibliothek

In Python 2.7 oder 3:

import unittest

In Python 2.6 können Sie einen Backport der unittestBibliothek von 2.7 mit dem Namen unittest2 installieren und den folgenden Alias ​​verwenden unittest:

import unittest2 as unittest

Beispieltests

Fügen Sie nun den folgenden Test der Typensicherheit von Python in Ihre Python-Shell ein:

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

Test One wird assertRaisesals Kontextmanager verwendet, der sicherstellt, dass der Fehler während der Aufzeichnung ordnungsgemäß abgefangen und bereinigt wird.

Wir könnten es auch ohne den Kontextmanager schreiben , siehe Test zwei. Das erste Argument ist der Fehlertyp, den Sie voraussichtlich auslösen werden, das zweite Argument, die zu testende Funktion und die verbleibenden Argumente und Schlüsselwortargumente werden an diese Funktion übergeben.

Ich denke, es ist viel einfacher, lesbarer und wartbarer, nur den Kontextmanager zu verwenden.

Ausführen der Tests

So führen Sie die Tests aus:

unittest.main(exit=False)

In Python 2.6 benötigen Sie wahrscheinlich Folgendes :

unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))

Und Ihr Terminal sollte Folgendes ausgeben:

..
----------------------------------------------------------------------
Ran 2 tests in 0.007s

OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>

Und wir sehen das wie erwartet, wenn wir versuchen, a 1und ein '1'Ergebnis in a hinzuzufügen TypeError.


Versuchen Sie Folgendes, um eine ausführlichere Ausgabe zu erhalten:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Aaron Hall
quelle
56

Ihr Code sollte diesem Muster folgen (dies ist ein ungeeigneter Test im Modulstil):

def test_afunction_throws_exception(self):
    try:
        afunction()
    except ExpectedException:
        pass
    except Exception:
       self.fail('unexpected exception raised')
    else:
       self.fail('ExpectedException not raised')

Unter Python <2.7 ist dieses Konstrukt nützlich, um in der erwarteten Ausnahme nach bestimmten Werten zu suchen. Die Funktion unittest assertRaisesprüft nur, ob eine Ausnahme ausgelöst wurde.

Daryl Spitzer
quelle
3
und Methode self.fail nimmt nur ein Argument
mdob
3
Dies scheint zu kompliziert zu sein, um zu testen, ob eine Funktion eine Ausnahme auslöst. Da jede andere Ausnahme als diese Ausnahme den Test fehlerhaft macht und das Nichtauslösen einer Ausnahme den Test nicht besteht, scheint der einzige Unterschied darin zu bestehen, dass Sie, wenn Sie eine andere Ausnahme mit assertRaiseserhalten, einen FEHLER anstelle eines FEHLERS erhalten.
Unflores
23

von: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/

Hier ist zunächst die entsprechende (noch dum: p) Funktion in der Datei 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

Hier ist der durchzuführende Test (nur dieser Test wird eingefügt):

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

Wir sind jetzt bereit, unsere Funktion zu testen! Folgendes passiert, wenn versucht wird, den Test auszuführen:

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

Der TypeError wird aktiv ausgelöst und generiert einen Testfehler. Das Problem ist, dass dies genau das Verhalten ist, das wir wollten: s.

Um diesen Fehler zu vermeiden, führen Sie einfach die Funktion mit Lambda im Testaufruf aus:

self.assertRaises(TypeError, lambda: df.square_value(self.false_int))

Die endgültige Ausgabe:

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Perfekt !

... und für mich ist es auch perfekt !!

Vielen Dank Herr Julien Lengrand-Lambert


Diese Testaussage gibt tatsächlich ein falsches Positiv zurück . Dies geschieht, weil das Lambda in 'assertRaises' die Einheit ist, die einen Typfehler auslöst, und nicht die getestete Funktion.

macm
quelle
10
Nur eine Anmerkung, Sie brauchen das Lambda nicht. Die Zeile self.assertRaises(TypeError, df.square_value(self.false_int))ruft die Methode auf und gibt das Ergebnis zurück. Was Sie wollen, ist, die Methode und alle Argumente zu übergeben und die Unittest es nennen zu lassen:self.assertRaises(TypeError, df.square_value, self.false_int)
Roman Kutlak
Vielen Dank. funktioniert perfekt
Chandan Kumar
14

Sie können Ihre eigenen erstellen contextmanager, um zu überprüfen, ob die Ausnahme ausgelöst wurde.

import contextlib

@contextlib.contextmanager
def raises(exception):
    try:
        yield 
    except exception as e:
        assert True
    else:
        assert False

Und dann können Sie so verwenden raises:

with raises(Exception):
    print "Hola"  # Calls assert False

with raises(Exception):
    raise Exception  # Calls assert True

Wenn Sie verwenden pytest, ist dieses Ding bereits implementiert. Sie können tun pytest.raises(Exception):

Beispiel:

def test_div_zero():
    with pytest.raises(ZeroDivisionError):
        1/0

Und das Ergebnis:

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
Pigueiras
quelle
1
Vielen Dank für die Veröffentlichung einer Antwort, für die das unittestModul nicht erforderlich ist !
Sherwood Callaway
10

Ich benutze doctest [1] fast überall, weil mir die Tatsache gefällt, dass ich meine Funktionen gleichzeitig dokumentiere und teste .

Schauen Sie sich diesen Code an:

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

Wenn Sie dieses Beispiel in ein Modul einfügen und über die Befehlszeile ausführen, werden beide Testfälle ausgewertet und überprüft.

[1] Python-Dokumentation: 23.2 doctest - Testen Sie interaktive Python-Beispiele

Pi.
quelle
4
Ich liebe doctest, aber ich finde, dass es eher unittest ergänzt als ersetzt.
TimothyAWiseman
2
Ist es weniger wahrscheinlich, dass doctest mit automatisiertem Refactoring gut spielt? Ich nehme an, ein für Python entwickeltes Refactoring-Tool sollte Docstrings kennen. Kann jemand aus seiner Erfahrung einen Kommentar abgeben?
Kdbanman
6

Ich habe gerade festgestellt, dass die Mock-Bibliothek eine assertRaisesWithMessage () -Methode (in ihrer unittest.TestCase-Unterklasse) bereitstellt, die nicht nur überprüft, ob die erwartete Ausnahme ausgelöst wird, sondern auch, ob sie mit der erwarteten Nachricht ausgelöst wird:

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)
Daryl Spitzer
quelle
Leider liefert es es nicht mehr. Aber die obige Antwort von @Art ( stackoverflow.com/a/3166985/1504046 ) liefert das gleiche Ergebnis
Rmatt
6

Hier gibt es viele Antworten. Der Code zeigt, wie wir eine Ausnahme erstellen können, wie wir diese Ausnahme in unseren Methoden verwenden können und wie Sie schließlich in einem Komponententest überprüfen können, ob die richtigen Ausnahmen ausgelöst werden.

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()
Arindam Roychowdhury
quelle
3

Sie können assertRaises aus dem unittest-Modul verwenden

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)
Bruno Carvalho
quelle
-5

Obwohl alle Antworten vollkommen in Ordnung sind, suchte ich nach einer Möglichkeit zu testen, ob eine Funktion eine Ausnahme auslöst, ohne sich auf Unit-Test-Frameworks zu verlassen und Testklassen schreiben zu müssen.

Am Ende schrieb ich Folgendes:

def assert_error(e, x):
    try:
        e(x)
    except:
        return
    raise AssertionError()

def failing_function(x):
    raise ValueError()

def dummy_function(x):
    return x

if __name__=="__main__":
    assert_error(failing_function, 0)
    assert_error(dummy_function, 0)

Und es scheitert auf der richtigen Linie:

Traceback (most recent call last):
  File "assert_error.py", line 16, in <module>
    assert_error(dummy_function, 0)
  File "assert_error.py", line 6, in assert_error
    raise AssertionError()
AssertionError
RUser4512
quelle