Jak testujesz, czy funkcja Python zgłasza wyjątek?


Odpowiedzi:


679

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)

9
czy istnieje sposób, aby zrobić coś przeciwnego? Czy to zawiedzie tylko wtedy, gdy funkcja zgłasza wyjątek?
BUInvent

67
Zauważ, że aby przekazać argumenty myfunc, musisz dodać je jako argumenty do wywołania assertRaises. Zobacz odpowiedź Daryla Spitzera.
cbron

1
Czy istnieje sposób na dopuszczenie wielu typów wyjątków?
Dinesh

1
Możesz użyć wbudowanych wyjątków Pythona, aby szybko przetestować asercję; za pomocą @ odpowiedzi Moe powyżej np 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
Raymond Wachaga

3
To samo dotyczy testowania konstruktorów klas:self.assertRaises(SomeCoolException, Constructor, arg1)
tschumann

476

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.exceptionw strprzeciwnym razie dostanieszTypeError

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

6
Korzystam z Python 2.7.10 i powyższe nie działa; context.exceptionnie przekazuje wiadomości; to jest typ.
LateCoder

6
Również w Pythonie 2.7 (przynajmniej w moim 2.7.6) przy użyciu import unittest2, musisz użyć str(), tj self.assertTrue('This is broken' in str(context.exception)).
Sohail Si

4
Dwie rzeczy: 1. Możesz użyć assertIn zamiast assertTrue. Np. Self.assertIn („This is broken”, context.exception) 2. W moim przypadku, w wersji 2.7.10, kontekst.exception wydaje się być tablicą znaków. Używanie str nie działa. Skończyło się na tym, że: '' .join (context.exception) Tak więc, razem: self.assertIn ('This is broken', '' .join (context.exception))
blockcipher

1
Czy to normalne, że twoja metoda zapycha konsolę testową za pomocą Traceback wyjątku? Jak temu zapobiec?
MadPhysicist

1
później znalazłem inny sposób, aby uzyskać komunikat jako ciąg wyjątku, jest to err = context.exception.message. A następnie można użyć również self.assertEqual (err, „This is broken”), aby wykonać test.
zhihong

326

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)

17
Drugi wycinek z tego, co zrobić, gdy argument zostanie przekazany, był bardzo pomocny.
Sabyasachi,

Używam 2.7.15. Jeśli afunctionin self.assertRaises(ExpectedException, afunction, arg1, arg2)jest inicjatorem klasy, musisz podać selfjako pierwszy argument, np. self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Minh Tran

128

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?

Krótka odpowiedź:

Użyj self.assertRaisesmetody jako menedżera kontekstu:

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

Demonstracja

Podejście oparte na najlepszych praktykach jest dość łatwe do wykazania w powłoce Pythona.

unittestbiblioteki

W Python 2.7 lub 3:

import unittest

W Pythonie 2.6 możesz zainstalować backport unittestbiblioteki 2.7 , o nazwie unittest2 , i po prostu alias, który unittest:

import unittest2 as unittest

Przykładowe testy

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 assertRaisesjako 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.

Przeprowadzanie testów

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 1i '1'wynik w TypeError.


Aby uzyskać bardziej szczegółowe dane wyjściowe, spróbuj tego:

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

56

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 assertRaisessprawdza tylko, czy zgłoszony został wyjątek.


3
a metoda self.fail przyjmuje tylko jeden argument
mdob 14.10

3
Wydaje się to zbyt skomplikowane do testowania, jeśli funkcja zgłasza wyjątek. Ponieważ jakikolwiek wyjątek inny niż ten wyjątek spowoduje błąd testu, a nie zgłoszenie wyjątku nie powiedzie się w teście, wydaje się, że jedyną różnicą jest to, że jeśli otrzymasz inny wyjątek assertRaises, otrzymasz BŁĄD zamiast AWARII.
rozjaśnia

23

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.


10
Tylko uwaga, nie potrzebujesz lambda. Linia 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)
Roman Kutlak,

Wielkie dzięki. działa idealnie
Chandan Kumar

14

Możesz zbudować własny, contextmanageraby 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 raisesten 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

1
Dziękujemy za opublikowanie odpowiedzi, która nie wymaga unittestmodułu!
Sherwood Callaway,

10

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


4
Uwielbiam doctest, ale uważam, że uzupełnia, a nie zastępuje.
TimothyAWiseman

2
Czy doctest rzadziej gra z automatycznym refaktoryzacją? Przypuszczam, że narzędzie refaktoryzujące zaprojektowane dla Pythona powinno być świadome dokumentów. Czy ktoś może komentować na podstawie swoich doświadczeń?
kdbanman

6

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)

Niestety, już tego nie zapewnia. Ale powyższa odpowiedź @Art ( stackoverflow.com/a/3166985/1504046 ) daje ten sam wynik
Rmatt

6

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

3

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)

-5

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.