Wie testen Sie, dass eine Python-Funktion eine Ausnahme auslöst?
-
02-07-2019 - |
Frage
Wie schreibt man einen Unittest, der nur dann, wenn eine Funktion ausfällt nicht eine erwartete Ausnahme auslösen?
Lösung
Verwenden TestCase.assertRaises
(oder TestCase.failUnlessRaises
) aus dem Modul Unittest zum Beispiel:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)
Andere Tipps
Seit Python 2.7 Sie Kontext-Manager verwenden können ahold des eigentlichen Exception-Objekt zu erhalten geworfen:
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/ Bibliothek / unittest.html # unittest.TestCase.assertRaises
In Python 3.5 , haben Sie context.exception
in str
wickeln, sonst werden Sie eine TypeError
bekommen
self.assertTrue('This is broken' in str(context.exception))
Der Code in meiner vorherigen Antwort kann vereinfacht werden:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
Und wenn eine Funktion Argumente übernimmt, so dass sie nur in assertRaises passieren wie folgt aus:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
Wie testen Sie, dass eine Python-Funktion löst eine Ausnahme?
Wie kann man einen Test schreiben, der nur schlägt fehl, wenn eine Funktion nicht ordnungsgemäß werfen eine erwartete Ausnahme?
Kurze Antwort:
Mit der self.assertRaises
Methode als Kontext-Manager:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
Demonstration
Der Best-Practice-Ansatz ist ziemlich einfach, in einem Python-Shell zu demonstrieren.
Die unittest
Bibliothek
In Python 2.7 oder 3:
import unittest
In Python 2.6 können Sie eine Rückportierung von 2,7 der unittest
Bibliothek installieren, genannt unittest2 und nur alias dass als unittest
:
import unittest2 as unittest
Beispiel Tests
Nun fügen Sie in Ihre Python-Shell den folgenden Test von Pythons Typsicherheit:
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 verwendet man assertRaises
als Kontext-Manager, der dafür sorgt, dass der Fehler richtig aufgefangen und gereinigt, während aufgezeichnet.
Wir könnten es auch schreiben, ohne Kontext-Manager, siehe Test zwei. Das erste Argument wäre der Fehlertyp, den Sie zu erhöhen erwarten, das zweite Argument, die Funktion Sie testen, und die restlichen args und Keyword-args wird an diese Funktion übergeben werden.
Ich denke, es ist viel einfacher, lesbar und wartbar nur den Kontext-Manager zu verwenden.
Ausführen der Tests
, um die Tests auszuführen:
unittest.main(exit=False)
In Python 2.6, werden Sie wahrscheinlich benötigen folgende :
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Und Ihr Terminal ausgeben soll wie folgt vor:
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s
OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>
Und wir sehen, dass, wie wir erwarten, versuchen, ein 1
und ein '1'
Ergebnis in einem TypeError
hinzuzufügen.
Für weitere ausführliche Ausgabe, versuchen Sie dies:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Der Code soll dieses Muster folgen (dies ist ein Unittest-Modul-Test):
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')
Ein Python <2.7 dieses Konstrukt zur Überprüfung nach bestimmten Werten in der erwarteten Ausnahme nützlich ist. Die Unittest-Funktion assertRaises
prüft nur, wenn eine Ausnahme ausgelöst wurde.
aus: http://www.lengrand.fr/2011 / 12 / pythonunittest-assertraises-raist-Fehler /
Als erstes ist hier 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 durchgeführt werden soll (nur dieser Test eingesetzt wird):
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 nun bereit, unsere Funktion zu testen! Hier ist, was passiert, wenn man versucht, 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)
Das ist Typeerror actullay erhöht und erzeugt ein Testversagen. Das Problem ist, dass dies genau das Verhalten, das wir wollten: s.
Um diesen Fehler zu vermeiden, führen Sie einfach die Funktion Lambda im Testanruf mit:
self.assertRaises(TypeError, lambda: df.square_value(self.false_int))
Die endgültige Ausgabe:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Perfect!
... und ist perfekt für mich auch !!
Thansk viel Herr Julien Lengrand-Lambert
Sie können Ihre eigenen contextmanager
zu überprüfen, ob die Exception ausgelöst wurde bauen.
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, wird diese Sache bereits umgesetzt. Sie können pytest.raises(Exception)
tun:
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
ich doctest [1] fast überall, weil ich mag die Tatsache, dass ich dokumentieren und testen Sie meine Funktionen zur gleichen Zeit.
Haben Sie einen Blick auf diesen Code:
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 einem Modul setzen und es von der Kommandozeile beiden Testfälle ausgeführt werden ausgewertet und überprüft.
[1] Python-Dokumentation: 23,2 doctest - Test interaktiver Python Beispiele
Haben Sie einen Blick auf die assertRaises Methode das unittest
Modul.
Ich habe gerade entdeckt, dass die Mock Bibliothek eine assertRaisesWithMessage () -Methode liefert ( in seiner unittest.TestCase Unterklasse), die nicht nur, dass die erwartete Ausnahme ausgelöst wird überprüft, sondern auch, dass es 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)
Sie können assertRaises vom 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)
Es gibt hier viele Antworten. Der Code zeigt, wie wir eine Ausnahme erstellen können, wie wir diese Ausnahme in unseren Methoden verwenden können, und schließlich, wie Sie in einem Unittest überprüfen können, die richtigen Ausnahmen angehoben 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()