Bagaimana Anda menguji bahwa fungsi Python melempar pengecualian?


Jawaban:


679

Gunakan TestCase.assertRaises(atau TestCase.failUnlessRaises) dari modul unittest, misalnya:

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)

9
adakah cara untuk melakukan yang sebaliknya? Suka gagal hanya jika fungsi tidak membuang pengecualian?
BUINvent

67
Perhatikan bahwa untuk meneruskan argumen, myfuncAnda harus menambahkannya sebagai argumen untuk panggilan assertRaises. Lihat jawaban Daryl Spitzer.
cbron

1
apakah ada cara untuk memungkinkan beberapa jenis pengecualian?
Dinesh

1
Anda bisa menggunakan Pengecualian Built-in Python untuk menguji pernyataan dengan cepat; menggunakan @ jawaban Moe di atas misalnya: self.assertRaises(TypeError, mymod.myfunc). Anda dapat menemukan daftar lengkap Pengecualian Bawaan
Raymond Wachaga

3
Hal yang sama untuk menguji konstruktor kelas:self.assertRaises(SomeCoolException, Constructor, arg1)
tschumann

476

Karena Python 2.7 Anda dapat menggunakan manajer konteks untuk mengetahui objek Exception yang sebenarnya dilemparkan:

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


Dalam Python 3.5 , Anda harus membungkus context.exceptiondi str, jika tidak, anda akan mendapatkanTypeError

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

6
Saya menggunakan Python 2.7.10, dan di atas tidak berfungsi; context.exceptiontidak memberikan pesan; itu adalah tipe.
LateCoder

6
Juga di Python 2.7 (setidaknya di 2.7.6 saya) menggunakan import unittest2, Anda perlu menggunakan str(), yaitu self.assertTrue('This is broken' in str(context.exception)).
Sohail Si

4
Dua hal: 1. Anda dapat menggunakan assertIn bukan assertTrue. Misalnya self.assertIn ('This is broken', context.exception) 2. Dalam kasus saya, menggunakan 2.7.10, context.exception tampaknya menjadi array karakter. Menggunakan str tidak bekerja. Saya akhirnya melakukan ini: '' .join (context.exception) Jadi, kumpulkan: self.assertIn ('This broken', '' .join (context.exception))
blockcipher

1
Apakah normal jika metode Anda menyumbat konsol uji dengan Traceback pengecualian? Bagaimana saya mencegah hal itu terjadi?
MadPhysicist

1
kemudian saya menemukan cara lain untuk mendapatkan pesan sebagai pengecualian, yaitu err = context.exception.message. Dan kemudian dapat menggunakan juga menggunakan self.assertEqual (err, 'This broken') untuk melakukan tes.
zhihong

326

Kode dalam jawaban saya sebelumnya dapat disederhanakan menjadi:

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

Dan jika fungsi mengambil argumen, cukup berikan mereka ke assertRaises seperti ini:

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

17
Yang kedua menyinggung tentang apa yang harus dilakukan ketika argumen disahkan sangat membantu.
Sabyasachi

Saya menggunakan 2.7.15. Jika afunctionin self.assertRaises(ExpectedException, afunction, arg1, arg2)adalah inisialisasi kelas, Anda harus memberikan selfargumen pertama misalnya, self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Minh Tran

128

Bagaimana Anda menguji bahwa fungsi Python melempar pengecualian?

Bagaimana seseorang menulis tes yang gagal hanya jika suatu fungsi tidak melempar pengecualian yang diharapkan?

Jawaban singkat:

Gunakan self.assertRaisesmetode ini sebagai manajer konteks:

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

Demonstrasi

Pendekatan praktik terbaik cukup mudah untuk ditunjukkan dalam shell Python.

The unittestlibrary

Dengan Python 2.7 atau 3:

import unittest

Dalam Python 2.6, Anda dapat menginstal backport unittestperpustakaan 2,7 , disebut unittest2 , dan hanya itu sebagai unittest:

import unittest2 as unittest

Contoh tes

Sekarang, tempel ke dalam kulit Python Anda tes berikut tentang keamanan tipe Python:

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

Uji satu menggunakan assertRaises sebagai manajer konteks, yang memastikan bahwa kesalahan ditangkap dan dibersihkan dengan benar, saat direkam.

Kita juga bisa menulisnya tanpa manajer konteks, lihat tes dua. Argumen pertama akan menjadi tipe kesalahan yang Anda harapkan untuk naik, argumen kedua, fungsi yang Anda uji, dan args yang tersisa serta args kata kunci akan diteruskan ke fungsi itu.

Saya pikir itu jauh lebih sederhana, mudah dibaca, dan dipelihara hanya dengan menggunakan manajer konteks.

Menjalankan tes

Untuk menjalankan tes:

unittest.main(exit=False)

Dalam Python 2.6, Anda mungkin perlu yang berikut :

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

Dan terminal Anda harus menampilkan yang berikut:

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

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

Dan kami melihat itu seperti yang kami harapkan, berusaha untuk menambahkan 1dan '1'menghasilkan dalam TypeError.


Untuk lebih banyak keluaran verbose, coba ini:

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

56

Kode Anda harus mengikuti pola ini (ini adalah tes gaya modul yang paling bebas):

def test_afunction_throws_exception(self):
    try:
        afunction()
    except ExpectedException:
        pass
    except Exception:
       self.fail('unexpected exception raised')
    else:
       self.fail('ExpectedException not raised')

Pada Python <2.7 konstruk ini berguna untuk memeriksa nilai-nilai spesifik dalam pengecualian yang diharapkan. Fungsi unittest assertRaiseshanya memeriksa apakah pengecualian muncul.


3
dan metode self.fail hanya membutuhkan satu argumen
mdob

3
Ini tampaknya terlalu rumit untuk pengujian jika suatu fungsi melempar pengecualian. Karena pengecualian selain pengecualian itu akan kesalahan tes dan tidak melempar pengecualian akan gagal tes, sepertinya satu-satunya perbedaan adalah bahwa jika Anda mendapatkan pengecualian yang berbeda dengan assertRaisesAnda akan mendapatkan KESALAHAN bukannya GAGAL.
unflores

23

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

Pertama, berikut adalah fungsi yang sesuai (masih dum: p) dalam file 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

Inilah tes yang harus dilakukan (hanya tes ini yang dimasukkan):

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

Kami sekarang siap untuk menguji fungsi kami! Inilah yang terjadi ketika mencoba menjalankan tes:

======================================================================
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 dinaikkan, dan menghasilkan kegagalan pengujian. Masalahnya adalah inilah perilaku yang kita inginkan: s.

Untuk menghindari kesalahan ini, cukup jalankan fungsi menggunakan lambda di panggilan tes:

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

Hasil akhir:

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

OK

Sempurna!

... dan bagiku juga sempurna !!

Thansk banyak Pak Julien Lengrand-Lambert


Tes ini menegaskan sebenarnya mengembalikan false positive . Itu terjadi karena lambda di dalam 'assertRaises' adalah unit yang memunculkan kesalahan tipe dan bukan fungsi yang diuji.


10
Hanya sebuah catatan, Anda tidak perlu lambda. Garis self.assertRaises(TypeError, df.square_value(self.false_int))memanggil metode dan mengembalikan hasilnya. Yang Anda inginkan adalah untuk lulus metode dan argumen apa pun dan biarkan yang paling tidak layak untuk menyebutnya:self.assertRaises(TypeError, df.square_value, self.false_int)
Roman Kutlak

Terima kasih banyak. berfungsi dengan baik
Chandan Kumar

14

Anda dapat membuat sendiri contextmanageruntuk memeriksa apakah pengecualian telah dinaikkan.

import contextlib

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

Dan kemudian Anda dapat menggunakan raisesseperti ini:

with raises(Exception):
    print "Hola"  # Calls assert False

with raises(Exception):
    raise Exception  # Calls assert True

Jika Anda menggunakan pytest, hal ini sudah diterapkan. Anda dapat melakukanpytest.raises(Exception) :

Contoh:

def test_div_zero():
    with pytest.raises(ZeroDivisionError):
        1/0

Dan hasilnya:

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
Terima kasih telah mengirim jawaban yang tidak memerlukan unittestmodul!
Sherwood Callaway

10

Saya menggunakan doctest [1] hampir di mana-mana karena saya suka fakta bahwa saya mendokumentasikan dan menguji fungsi saya pada saat yang sama.

Lihatlah kode ini:

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

Jika Anda meletakkan contoh ini dalam modul dan menjalankannya dari baris perintah, kedua kasus uji dievaluasi dan diperiksa.

[1] Dokumentasi Python: 23.2 doctest - Uji contoh Python interaktif


4
Saya suka doctest, tetapi saya menemukan itu bukan pengganti pengganti unitest.
TimothyAWiseman

2
Apakah doctest cenderung bermain bagus dengan refactoring otomatis? Saya kira alat refactoring yang dirancang untuk python harus menyadari docstrings. Adakah yang bisa berkomentar dari pengalaman mereka?
kdbanman

6

Saya baru saja menemukan bahwa perpustakaan Mock menyediakan metode assertRaisesWithMessage () (dalam subclass unittest.TestCase), yang akan memeriksa tidak hanya bahwa pengecualian yang diharapkan dinaikkan, tetapi juga yang dimunculkan dengan pesan yang diharapkan:

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)

Sayangnya, ia tidak menyediakannya lagi .. Tetapi jawaban @Art ( stackoverflow.com/a/3166985/1504046 ) di atas memberikan hasil yang sama
Rmatt

6

Ada banyak jawaban di sini. Kode menunjukkan bagaimana kita dapat membuat Pengecualian, bagaimana kita dapat menggunakan pengecualian itu dalam metode kami, dan akhirnya, bagaimana Anda dapat memverifikasi dalam pengujian unit, pengecualian yang benar dimunculkan.

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

Anda dapat menggunakan assertRaises dari modul 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)

-5

Sementara semua jawaban baik-baik saja, saya mencari cara untuk menguji apakah suatu fungsi menimbulkan pengecualian tanpa bergantung pada kerangka kerja unit testing dan harus menulis kelas tes.

Saya akhirnya menulis yang berikut ini:

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)

Dan gagal di jalur yang benar:

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.