Jak napisać unittest, który zawiedzie tylko wtedy, gdy funkcja nie zgłosi oczekiwanego wyjątku?
Jak napisać unittest, który zawiedzie tylko wtedy, gdy funkcja nie zgłosi oczekiwanego wyjątku?
Odpowiedzi:
Użyj TestCase.assertRaises
(lub TestCase.failUnlessRaises
) z najczystszego modułu, na przykład:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)
myfunc
, musisz dodać je jako argumenty do wywołania assertRaises. Zobacz odpowiedź Daryla Spitzera.
self.assertRaises(TypeError, mymod.myfunc)
. Pełną listę wbudowanych wyjątków można znaleźć tutaj: docs.python.org/3/library/exceptions.html#bltin-exceptions
self.assertRaises(SomeCoolException, Constructor, arg1)
Od wersji Python 2.7 można użyć menedżera kontekstu, aby uzyskać dostęp do rzuconego obiektu wyjątku:
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
W Pythonie 3.5 musisz się owinąć, context.exception
w str
przeciwnym razie dostanieszTypeError
self.assertTrue('This is broken' in str(context.exception))
context.exception
nie przekazuje wiadomości; to jest typ.
import unittest2
, musisz użyć str()
, tj self.assertTrue('This is broken' in str(context.exception))
.
Kod w mojej poprzedniej odpowiedzi można uprościć do:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
A jeśli funkcja pobiera argumenty, po prostu przekaż je do assertRaises w następujący sposób:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
2.7.15
. Jeśli afunction
in self.assertRaises(ExpectedException, afunction, arg1, arg2)
jest inicjatorem klasy, musisz podać self
jako pierwszy argument, np. self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Jak testujesz, czy funkcja Python zgłasza wyjątek?
Jak napisać test, który kończy się niepowodzeniem tylko wtedy, gdy funkcja nie zgłasza oczekiwanego wyjątku?
Użyj self.assertRaises
metody jako menedżera kontekstu:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
Podejście oparte na najlepszych praktykach jest dość łatwe do wykazania w powłoce Pythona.
unittest
biblioteki
W Python 2.7 lub 3:
import unittest
W Pythonie 2.6 możesz zainstalować backport unittest
biblioteki 2.7 , o nazwie unittest2 , i po prostu alias, który unittest
:
import unittest2 as unittest
Teraz wklej do powłoki Pythona następujący test bezpieczeństwa typu Pythona:
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 używany jest assertRaises
jako menedżer kontekstu, który zapewnia, że błąd jest odpowiednio wychwytywany i usuwany podczas rejestrowania.
Możemy to również napisać bez menedżera kontekstu, patrz test drugi. Pierwszym argumentem byłby typ błędu, którego się spodziewasz, drugim argumentem, testowana funkcja, a pozostałe argumenty i słowa kluczowe args zostaną przekazane do tej funkcji.
Myślę, że korzystanie z menedżera kontekstu jest znacznie prostsze, czytelniejsze i łatwiejsze w utrzymaniu.
Aby uruchomić testy:
unittest.main(exit=False)
W Pythonie 2.6 prawdopodobnie potrzebujesz :
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Twój terminal powinien wypisać następujące dane:
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s
OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>
I widzimy to, jak się spodziewamy, próbując dodać a 1
i '1'
wynik w TypeError
.
Aby uzyskać bardziej szczegółowe dane wyjściowe, spróbuj tego:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Twój kod powinien być zgodny z tym wzorcem (jest to najszczerszy test stylu modułu):
def test_afunction_throws_exception(self):
try:
afunction()
except ExpectedException:
pass
except Exception:
self.fail('unexpected exception raised')
else:
self.fail('ExpectedException not raised')
W Pythonie <2.7 ta konstrukcja jest przydatna do sprawdzania określonych wartości w oczekiwanym wyjątku. Unittest funkcja assertRaises
sprawdza tylko, czy zgłoszony został wyjątek.
assertRaises
, otrzymasz BŁĄD zamiast AWARII.
od: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/
Po pierwsze, tutaj jest odpowiednia funkcja (wciąż dum: p) w pliku 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
Oto test, który należy wykonać (wstawiony jest tylko ten test):
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()
Jesteśmy teraz gotowi do przetestowania naszej funkcji! Oto, co dzieje się podczas próby uruchomienia testu:
======================================================================
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)
Błąd TypeError został zgłoszony i generuje błąd testu. Problem polega na tym, że właśnie takiego zachowania chcieliśmy: s.
Aby uniknąć tego błędu, po prostu uruchom funkcję za pomocą lambda w wywołaniu testowym:
self.assertRaises(TypeError, lambda: df.square_value(self.false_int))
Ostateczny wynik:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Doskonały !
... i dla mnie też jest idealny !!
Dziękuję bardzo, panie Julien Lengrand-Lambert
Ten test faktycznie zwraca fałszywie dodatni . Dzieje się tak, ponieważ lambda wewnątrz „assertRaises” to jednostka, która podnosi błąd typu, a nie testowana funkcja.
self.assertRaises(TypeError, df.square_value(self.false_int))
wywołuje metodę i zwraca wynik. To, czego chcesz, to przekazać metodę i wszelkie argumenty i niech najgorsi mogą ją nazwać:self.assertRaises(TypeError, df.square_value, self.false_int)
Możesz zbudować własny, contextmanager
aby sprawdzić, czy wyjątek został zgłoszony.
import contextlib
@contextlib.contextmanager
def raises(exception):
try:
yield
except exception as e:
assert True
else:
assert False
A potem możesz użyć w raises
ten sposób:
with raises(Exception):
print "Hola" # Calls assert False
with raises(Exception):
raise Exception # Calls assert True
Jeśli używasz pytest
, ta rzecz jest już zaimplementowana. Możesz zrobić pytest.raises(Exception)
:
Przykład:
def test_div_zero():
with pytest.raises(ZeroDivisionError):
1/0
A wynik:
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
unittest
modułu!
Korzystam z doctest [1] prawie wszędzie, ponieważ podoba mi się to, że jednocześnie dokumentuję i testuję swoje funkcje.
Spójrz na ten kod:
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()
Jeśli umieścisz ten przykład w module i uruchomisz go z wiersza poleceń, oba przypadki testowe zostaną ocenione i sprawdzone.
[1] Dokumentacja w języku Python: 23.2 doctest - Testuj interaktywne przykłady w języku Python
Właśnie odkryłem, że biblioteka Mock udostępnia metodę assertRaisesWithMessage () (w jej podklasie unittest.TestCase), która sprawdzi nie tylko, czy zgłoszony został oczekiwany wyjątek, ale również zgłoszony komunikat:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
'expected message',
mymod.myfunc)
Tutaj jest wiele odpowiedzi. Kod pokazuje, w jaki sposób możemy stworzyć wyjątek, jak możemy wykorzystać ten wyjątek w naszych metodach, a na koniec, w jaki sposób można zweryfikować w teście jednostkowym, czy zgłaszane są poprawne wyjątki.
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()
Możesz użyć assertRaises z najbardziej nieprzystosowanego modułu
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)
Chociaż wszystkie odpowiedzi są w porządku, szukałem sposobu na sprawdzenie, czy funkcja zgłosiła wyjątek bez polegania na strukturach testów jednostkowych i konieczności pisania klas testowych.
Skończyło się na tym, że napisałem:
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)
I nie powiedzie się na właściwej linii:
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