Làm thế nào để bạn kiểm tra rằng một hàm Python ném một ngoại lệ?


Câu trả lời:


679

Sử dụng TestCase.assertRaises(hoặc TestCase.failUnlessRaises) từ mô-đun nhỏ nhất, ví dụ:

import mymod

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

9
Có cách nào để làm ngược lại với điều này? Giống như nó chỉ thất bại nếu chức năng ném ngoại lệ?
BUInvent

67
Lưu ý rằng để truyền đối số cho myfuncbạn cần thêm chúng làm đối số cho lệnh gọi assertRaises. Xem câu trả lời của Daryl Spitzer.
cbron

1
Có cách nào để cho phép nhiều loại ngoại lệ không?
Dinesh

1
Bạn có thể sử dụng Ngoại lệ tích hợp của Python để nhanh chóng kiểm tra xác nhận; sử dụng câu trả lời của @ Moe ở trên chẳng hạn : self.assertRaises(TypeError, mymod.myfunc). Bạn có thể tìm thấy một danh sách đầy đủ các Ngoại lệ Built-in ở đây: docs.python.org/3/library/exceptions.html#bltin-exceptions
Raymond Wachaga

3
Điều tương tự để kiểm tra các nhà xây dựng lớp:self.assertRaises(SomeCoolException, Constructor, arg1)
tschumann

476

Vì Python 2.7, bạn có thể sử dụng trình quản lý bối cảnh để nhận được đối tượng Exception thực tế bị ném:

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/l Library / unittest.html # unittest.TestCase.assertRaises


Trong Python 3.5 , bạn phải quấn context.exceptiontrong str, nếu không bạn sẽ nhận được mộtTypeError

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

6
Tôi đang sử dụng Python 2.7.10 và cách trên không hoạt động; context.exceptionkhông đưa ra thông điệp; nó là một loại
lateCoder

6
Ngoài ra trong Python 2.7 (ít nhất là trong 2.7.6 của tôi) import unittest2, bạn cần sử dụng str(), tức là self.assertTrue('This is broken' in str(context.exception)).
Sohail Si

4
Hai điều: 1. Bạn có thể sử dụng assertIn thay vì assertTrue. Ví dụ: self.assertIn ('Điều này bị hỏng', bối cảnh.exception) 2. Trong trường hợp của tôi, sử dụng 2.7.10, bối cảnh.exception dường như là một mảng các ký tự. Sử dụng str không hoạt động. Cuối cùng tôi đã làm điều này: '' .join (bối cảnh.exception) Vì vậy, hãy kết hợp lại với nhau: self.assertIn ('Cái này bị hỏng', '' .join (bối cảnh.exception))
mã hóa

1
Có phải bình thường là phương pháp của bạn làm tắc nghẽn bảng điều khiển thử nghiệm với TracBack của ngoại lệ không? Làm thế nào để tôi ngăn chặn điều đó xảy ra?
MadPhysicist

1
sau đó tôi đã tìm thấy một cách khác để lấy thông điệp dưới dạng str của ngoại lệ, đó là err = context.exception.message. Và sau đó cũng có thể sử dụng self.assertEqual (err, 'This is broken') để làm bài kiểm tra.
zhihong

326

Mã trong câu trả lời trước của tôi có thể được đơn giản hóa thành:

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

Và nếu giao thức có các đối số, chỉ cần chuyển chúng vào các xác nhận như thế này:

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

17
Đoạn trích thứ hai về những việc cần làm khi tranh luận được thông qua thực sự hữu ích.
Sabyasachi

Tôi đang sử dụng 2.7.15. Nếu afunctiontrong self.assertRaises(ExpectedException, afunction, arg1, arg2)là trình khởi tạo lớp, bạn cần chuyển qua selflàm đối số đầu tiên, ví dụ: self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Minh Trần

128

Làm thế nào để bạn kiểm tra rằng một hàm Python ném một ngoại lệ?

Làm thế nào để người ta viết một bài kiểm tra thất bại chỉ khi một hàm không đưa ra một ngoại lệ dự kiến?

Câu trả lời ngắn:

Sử dụng self.assertRaisesphương thức làm trình quản lý bối cảnh:

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

Trình diễn

Cách tiếp cận thực hành tốt nhất là khá dễ dàng để chứng minh trong trình bao Python.

các unittestthư viện

Trong Python 2.7 hoặc 3:

import unittest

Trong Python 2.6, bạn có thể cài đặt một backport của unittestthư viện 2.7 , được gọi là unittest2 và chỉ là bí danh như unittest:

import unittest2 as unittest

Ví dụ kiểm tra

Bây giờ, dán vào trình bao Python của bạn trong bài kiểm tra sau về độ an toàn của 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')

Kiểm tra một sử dụng assertRaisesnhư một trình quản lý bối cảnh, đảm bảo rằng lỗi được bắt đúng và được dọn sạch, trong khi được ghi lại.

Chúng tôi cũng có thể viết nó mà không cần trình quản lý bối cảnh, xem thử nghiệm hai. Đối số đầu tiên sẽ là loại lỗi bạn mong muốn nêu ra, đối số thứ hai, chức năng bạn đang kiểm tra và các đối số và đối số từ khóa còn lại sẽ được chuyển đến chức năng đó.

Tôi nghĩ rằng nó đơn giản hơn nhiều, có thể đọc và duy trì để sử dụng trình quản lý bối cảnh.

Chạy thử nghiệm

Để chạy thử nghiệm:

unittest.main(exit=False)

Trong Python 2.6, có thể bạn sẽ cần những điều sau :

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

Và thiết bị đầu cuối của bạn sẽ xuất ra như sau:

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

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

Và chúng ta thấy rằng như chúng ta mong đợi, cố gắng thêm một 1'1'kết quả trong một TypeError.


Để biết thêm đầu ra dài dòng, hãy thử điều này:

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

56

Mã của bạn phải tuân theo mẫu này (đây là thử nghiệm kiểu mô-đun không đáng tin cậy nhất):

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

Trên Python <2.7 cấu trúc này rất hữu ích để kiểm tra các giá trị cụ thể trong ngoại lệ dự kiến. Hàm unittest assertRaiseschỉ kiểm tra nếu một ngoại lệ được đưa ra.


3
và phương thức self.fail chỉ mất một đối số
mdob

3
Điều này có vẻ quá phức tạp để kiểm tra nếu một hàm ném ngoại lệ. Vì bất kỳ ngoại lệ nào ngoại trừ ngoại lệ đó sẽ làm lỗi bài kiểm tra và không ném ngoại lệ sẽ thất bại bài kiểm tra, có vẻ như sự khác biệt duy nhất là nếu bạn có một ngoại lệ khác với assertRaisesbạn sẽ nhận được LRI thay vì FAIL.
mở ra

23

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

Đầu tiên, đây là hàm tương ứng (still dum: p) trong tệp dum_feft.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

Đây là bài kiểm tra cần thực hiện (chỉ bài kiểm tra này được chèn):

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

Bây giờ chúng tôi đã sẵn sàng để kiểm tra chức năng của chúng tôi! Đây là những gì xảy ra khi cố gắng chạy thử nghiệm:

======================================================================
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 được nâng cấp và tạo ra lỗi thử nghiệm. Vấn đề là đây chính xác là hành vi mà chúng tôi muốn: s.

Để tránh lỗi này, chỉ cần chạy chức năng bằng lambda trong cuộc gọi thử nghiệm:

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

Đầu ra cuối cùng:

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

OK

Hoàn hảo !

... Và đối với tôi cũng hoàn hảo !!

Rất nhiều ông Julien Lengrand-Lambert


Thử nghiệm này khẳng định thực sự trả về một dương tính giả . Điều đó xảy ra bởi vì lambda bên trong 'assertRaises' là đơn vị làm tăng lỗi loại và không phải là chức năng được kiểm tra.


10
Chỉ cần một lưu ý, bạn không cần lambda. Dòng self.assertRaises(TypeError, df.square_value(self.false_int))gọi phương thức và trả về kết quả. Những gì bạn muốn là vượt qua phương thức và bất kỳ đối số nào và để cho người ít nhất gọi nó:self.assertRaises(TypeError, df.square_value, self.false_int)
Roman Kutlak

Cảm ơn rất nhiều. hoạt động hoàn hảo
Chandan Kumar

14

Bạn có thể tự xây dựng contextmanagerđể kiểm tra xem ngoại lệ có được nêu ra không.

import contextlib

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

Và sau đó bạn có thể sử dụng raisesnhư thế này:

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

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

Nếu bạn đang sử dụng pytest, điều này đã được thực hiện. Bạn có thể làm pytest.raises(Exception):

Thí dụ:

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

Và kết quả:

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
Cảm ơn bạn đã đăng câu trả lời không yêu cầu unittestmô-đun!
Sherwood Callaway

10

Tôi sử dụng doctest [1] gần như ở mọi nơi vì tôi thích thực tế là tôi tài liệu và kiểm tra các chức năng của mình cùng một lúc.

Hãy xem mã này:

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

Nếu bạn đặt ví dụ này trong một mô-đun và chạy nó từ dòng lệnh, cả hai trường hợp kiểm tra đều được ước tính và kiểm tra.

[1] Tài liệu Python: 23.2 doctest - Kiểm tra các ví dụ Python tương tác


4
Tôi yêu doctest, nhưng tôi thấy nó bổ sung hơn là thay thế không đáng tin cậy.
TimothyAWiseman

2
Là doctest ít có khả năng chơi đẹp với tái cấu trúc tự động? Tôi cho rằng một công cụ tái cấu trúc được thiết kế cho python nên nhận thức được các tài liệu. Bất cứ ai có thể nhận xét từ kinh nghiệm của họ?
kdbanman

6

Tôi vừa phát hiện ra rằng thư viện Mock cung cấp một phương thức assertRaisesWithMessage () (trong lớp con unittest.TestCase của nó), nó sẽ kiểm tra không chỉ ngoại lệ dự kiến ​​được đưa ra, mà còn được nêu ra với thông điệp dự kiến:

from testcase import TestCase

import mymod

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

Thật không may, nó không cung cấp cho nó nữa .. Nhưng câu trả lời ở trên của @Art ( stackoverflow.com/a/3166985/1504046 ) cho kết quả tương tự
Rmatt

6

Có rất nhiều câu trả lời ở đây. Mã này cho thấy cách chúng tôi có thể tạo Ngoại lệ, cách chúng tôi có thể sử dụng ngoại lệ đó trong các phương thức của mình và cuối cùng, cách bạn có thể xác minh trong thử nghiệm đơn vị, các ngoại lệ chính xác được nêu ra.

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

Bạn có thể sử dụng assertRaises từ mô-đun nhỏ nhất

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

Trong khi tất cả các câu trả lời là hoàn toàn tốt, tôi đã tìm cách để kiểm tra xem một hàm có đưa ra một ngoại lệ mà không dựa vào các khung kiểm thử đơn vị và phải viết các lớp kiểm tra hay không.

Cuối cùng tôi đã viết như sau:

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)

Và nó thất bại ở đúng dòng:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.