Jak sprawdzić, czy funkcja Pythona rzuca wyjątek?

Jak napisać unittest, który nie powiedzie się tylko wtedy, gdy funkcja nie wyrzuci oczekiwanego wyjątku?

Author: Aaron Hall, 2008-09-25

11 answers

Użycie TestCase.assertRaises (lub TestCase.failUnlessRaises) z modułu unittest, na przykład:

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)
 451
Author: Moe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-05 15:59:38

Od wersji Python 2.7 można użyć menedżera kontekstu, aby uzyskać dostęp do rzeczywistego obiektu wyjątku rzuconego:

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 Python 3.5 , musisz zawinąć context.exception w str, w przeciwnym razie otrzymasz TypeError

self.assertTrue('This is broken' in str(context.exception))
 320
Author: Art,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-20 06:26:18

Kod w mojej poprzedniej odpowiedzi można uprościć do:

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

A jeśli afunkcja pobiera argumenty, po prostu przekaż je do twierdzeń w następujący sposób:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)
 207
Author: Daryl Spitzer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-18 13:37:56

Jak sprawdzić, czy funkcja Pythona rzuca wyjątek?

Jak napisać test, który nie powiedzie się tylko wtedy, gdy funkcja nie rzuci oczekiwany wyjątek?

Krótka Odpowiedź:

Użyj metody self.assertRaises 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 zademonstrowania w powłoce Pythona.

Biblioteka unittest

W Pythonie 2.7 lub 3:

import unittest

W Pythonie 2.6 możesz zainstalować backport biblioteki unittest 2.7 o nazwie unittest2 , i po prostu alias 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 one używa assertRaises jako menedżera kontekstu, który zapewnia, że błąd zostanie prawidłowo wychwycony i wyczyszczony podczas nagrywania.

Możemy go również napisać BEZ menedżera kontekstu, zobacz test drugi. Pierwszym argumentem będzie typ błędu, który chcesz wywołać, drugim argument, funkcja, którą testujesz, a pozostałe args i słowo kluczowe args zostaną przekazane do tej funkcji.

Myślę, że o wiele prostsze, czytelniejsze i łatwiejsze do utrzymania jest użycie Menedżera kontekstu.

Uruchamianie testów

Aby uruchomić testy:

unittest.main(exit=False)

W Pythonie 2.6 prawdopodobnie będziesz potrzebował następującej :

unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))

I twój terminal powinien wyjście:

..
----------------------------------------------------------------------
Ran 2 tests in 0.007s

OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>

I widzimy to tak, jak się spodziewamy, próbując dodać 1 i '1' w wyniku TypeError.


Aby uzyskać więcej informacji, spróbuj tego:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
 95
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:43

Twój kod powinien podążać za tym wzorcem (jest to test stylu modułu unittest):

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

W Pythonie assertRaises sprawdza tylko, czy został zgłoszony wyjątek.

 30
Author: Daryl Spitzer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-05 16:08:52

From: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/

Po pierwsze, tutaj jest odpowiednia (jeszcze dum :p) funkcja 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 do wykonania (tylko ten test jest wstawiony):

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 się dzieje 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)

TypeError jest wywoływany przez actullay i generuje błąd testu. Problem w tym, że jest to dokładnie zachowanie, którego chcieliśmy: s.

Aby uniknąć tego błędu, po prostu uruchom funkcję lambda w wywołaniu testowym:

self.assertRaises(TypeError, lambda: df.square_value(self.false_int))

Wynik końcowy:

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
Doskonale !

... i dla mnie też jest idealny!!

Thansk a lot Mr. Julien Lengrand-Lambert

 12
Author: macm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-05 17:04:37

Używam doctest [1] prawie wszędzie, ponieważ podoba mi się fakt, że jednocześnie dokumentuję i testuję moje 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 linii poleceń, oba przypadki testowe są oceniane i sprawdzane.

[1] Dokumentacja Pythona: 23.2 doctest -- test interaktywnych przykładów Pythona

 8
Author: pi.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 11:17:18

Spójrz na metodę assertRaises modułu unittest.

 7
Author: Greg Hewgill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-21 12:57:25

Właśnie odkryłem, że Mock library dostarcza metody assertRaisesWithMessage () (w swojej jednostce.TestCase subclass), która sprawdzi nie tylko, czy wywołany jest oczekiwany wyjątek, ale także czy został wywołany z oczekiwaną wiadomością:

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)
 5
Author: Daryl Spitzer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-28 00:13:39

Możesz zbudować swój własny contextmanager, aby sprawdzić, czy wyjątek został podniesiony.

import contextlib

@contextlib.contextmanager
def raises(exception):
    try:
        yield 
    except exception as e:
        assert True
    else:
        assert False

I wtedy możesz użyć raises w 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

I 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
 5
Author: Pigueiras,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-18 10:09:17

Możesz użyć assertRaises z modułu unittest

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)
 1
Author: Bruno Carvalho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-21 00:12:46