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.