it-swarm.com.de

Wie testen Sie, dass eine Python -Funktion eine Ausnahme auslöst?

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

669
Daryl Spitzer

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)
_
575
Moe

Seit Python 2.7 können Sie den Kontext-Manager verwenden, um einen Überblick über das tatsächlich ausgelöste Exception-Objekt zu erhalten:

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 __== '__main__':
    unittest.main()

http://docs.python.org/dev/library/unittest.html#unittest.TestCase.assertRaises


In Python 3.5 müssen Sie context.exception in str einschließen, andernfalls erhalten Sie TypeError

self.assertTrue('This is broken' in str(context.exception))
415
Art

Der Code in meiner vorherigen Antwort kann vereinfacht werden:

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

Und wenn eine Funktion Argumente akzeptiert, übergeben Sie sie einfach an assertRaises:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)
275
Daryl Spitzer

Wie testen Sie, ob eine Python -Funktion eine Ausnahme auslöst?

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

Kurze Antwort:

Verwenden Sie die self.assertRaises -Methode als Kontextmanager:

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

Demonstration

Der Best-Practice-Ansatz ist in einer Python -Shell recht einfach zu demonstrieren.

Die unittest Bibliothek

In Python 2.7 oder 3:

import unittest

In Python 2.6 können Sie einen Backport der unittest -Bibliothek von 2.7 mit dem Namen nittest2 installieren, und zwar als unittest:

import unittest2 as unittest

Beispieltests

Fügen Sie nun in Ihre Python Shell den folgenden Test der Python-Typensicherheit 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 eins verwendet assertRaises als Kontextmanager, der sicherstellt, dass der Fehler während der Aufzeichnung richtig abgefangen und bereinigt wird.

Wir könnten es auch schreiben ohne den Kontextmanager, siehe Test zwei. Das erste Argument ist der erwartete Fehlertyp, 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 Kontext-Manager zu verwenden.

Ausführen der Tests

So führen Sie die Tests durch:

unittest.main(exit=False)

In Python 2.6 werden Sie wahrscheinlich Folgendes benötigen :

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, dass der Versuch, einen 1 und einen '1' hinzuzufügen, erwartungsgemäß zu einem TypeError führt.


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

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

Ihr Code sollte diesem Muster folgen (dies ist ein unanständiger Modultest):

def test_afunction_throws_exception(self):
    try:
        afunction()
    except ExpectedException:
        pass
    except Exception as e:
       self.fail('Unexpected exception raised:', e)
    else:
       self.fail('ExpectedException not raised')

In Python <2.7 ist dieses Konstrukt hilfreich, um in der erwarteten Ausnahme nach bestimmten Werten zu suchen. Die unittest-Funktion assertRaises prüft nur, ob eine Ausnahme ausgelöst wurde.

47
Daryl Spitzer

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 Test durchzuführen (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 __== "__main__":
       unittest.main()

Jetzt können wir unsere Funktion testen! Folgendes passiert, wenn Sie versuchen, 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 automatisch 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 die Funktion im Testaufruf einfach mit Lambda 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 das auch perfekt !!

Vielen Dank Herr Julien Lengrand-Lambert

16
macm

Sie können Ihr eigenes contextmanager erstellen, 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 raises wie folgt verwenden:

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

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

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

Beispiel:

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

Und das Ergebnis:

[email protected]$ 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
11
Pigueiras

Ich verwende 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 __== '__main__':
    import doctest
    doctest.testmod()

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

[1] Python-Dokumentation: 23.2 doctest - Interaktive Python Beispiele testen

10
pi.

Schauen Sie sich die assertRaises Methode des unittest Moduls an.

9
Greg Hewgill

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

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)
6
Daryl Spitzer

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)
2
Bruno Carvalho

Hier gibt es viele Antworten. Der Code zeigt, wie wir eine Ausnahmebedingung erstellen können, wie wir diese Ausnahmebedingung in unseren Methoden verwenden können und schließlich, wie Sie in einem Unittest überprüfen können, ob die richtigen Ausnahmebedingungen 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 __== '__main__':
    unittest.main()