คุณจะทดสอบว่าฟังก์ชัน Python ส่งข้อยกเว้นได้อย่างไร


คำตอบ:


679

ใช้TestCase.assertRaises(หรือTestCase.failUnlessRaises) จากโมดูล unittest ตัวอย่างเช่น:

import mymod

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

9
มีวิธีทำในสิ่งที่ตรงกันข้ามหรือไม่? ชอบมันล้มเหลวเฉพาะในกรณีที่ฟังก์ชั่นจะโยนข้อยกเว้น?
BUInvent

67
โปรดทราบว่าการส่งผ่านข้อโต้แย้งไปยังmyfuncคุณต้องเพิ่มเป็นข้อโต้แย้งไปยังการเรียก assertRaises ดูคำตอบของ Daryl Spitzer
cbron

1
มีวิธีการอนุญาตสำหรับข้อยกเว้นหลายประเภทหรือไม่
Dinesh

1
คุณสามารถใช้ข้อยกเว้นในตัวของ Python เพื่อทดสอบการยืนยันอย่างรวดเร็ว โดยใช้ @ self.assertRaises(TypeError, mymod.myfunc)คำตอบของหม้อข้างต้นตัวอย่างเช่น: คุณสามารถดูรายการข้อยกเว้นในตัวทั้งหมดได้ที่นี่: docs.python.org/3/library/exceptions.html#bltin-exceptions
Raymond Wachaga

3
สิ่งเดียวกันสำหรับผู้สร้างคลาสการทดสอบ:self.assertRaises(SomeCoolException, Constructor, arg1)
tschumann

476

เนื่องจาก Python 2.7 คุณสามารถใช้ตัวจัดการบริบทเพื่อรับ ahold ของวัตถุ Exception ที่เกิดขึ้นจริง:

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


ในPython 3.5คุณต้องห่อcontext.exceptionในstrมิฉะนั้นคุณจะได้รับTypeError

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

6
ฉันใช้ Python 2.7.10 และข้างต้นใช้งานไม่ได้ context.exceptionไม่ให้ข้อความ มันเป็นประเภท
LateCoder

6
นอกจากนี้ใน Python 2.7 (อย่างน้อยใน 2.7.6 ของฉัน) ใช้import unittest2คุณจะต้องใช้คือstr() self.assertTrue('This is broken' in str(context.exception))
Sohail Si

4
สองสิ่ง: 1. คุณสามารถใช้ assertIn แทน assertTrue เช่น self.assertIn ('นี่เสีย', context.exception) 2. ในกรณีของฉันใช้ 2.7.10, context.exception ดูเหมือนจะเป็นอาร์เรย์ของตัวละคร การใช้ str ไม่ทำงาน ฉันลงเอยด้วยการทำสิ่งนี้: '' .join (context.exception) ดังนั้นรวบรวม: self.assertIn ('นี่เสีย', '' .join (context.exception))
blockcipher

1
เป็นเรื่องปกติหรือไม่ที่วิธีการของคุณอุดตันคอนโซลทดสอบด้วย Traceback ของข้อยกเว้น ฉันจะป้องกันสิ่งนั้นไม่ให้เกิดขึ้นได้อย่างไร
MadPhysicist

1
ต่อมาฉันพบวิธีอื่นในการรับข้อความเป็น str ของข้อยกเว้นมันคือ err = context.exception.message และจากนั้นยังสามารถใช้ self.assertEqual (err, 'This is broken') เพื่อทำการทดสอบ
zhihong

326

รหัสในคำตอบก่อนหน้าของฉันสามารถทำให้ง่ายขึ้นเพื่อ:

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

และหากการทะเลาะกันนั้นขัดแย้งกันแค่ส่งพวกเขาไปยัง assertRaises เช่นนี้

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

17
สิ่งที่สองที่เกี่ยวกับสิ่งที่ต้องทำเมื่อการโต้แย้งผ่านเป็นประโยชน์จริงๆ
Sabyasachi

2.7.15ฉันใช้ หากafunctionในself.assertRaises(ExpectedException, afunction, arg1, arg2)คือการเริ่มต้นเรียนคุณต้องผ่านการselfเป็นเช่นอาร์กิวเมนต์แรก self.assertRaises(ExpectedException, Class, self, arg1, arg2)
มินห์ Tran

128

คุณจะทดสอบว่าฟังก์ชัน Python ส่งข้อยกเว้นได้อย่างไร

หนึ่งจะเขียนการทดสอบที่ล้มเหลวเฉพาะในกรณีที่ฟังก์ชั่นไม่ได้โยนข้อยกเว้นที่คาดไว้?

คำตอบสั้น ๆ :

ใช้self.assertRaisesวิธีการเป็นผู้จัดการบริบท:

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

สาธิต

วิธีปฏิบัติที่ดีที่สุดนั้นค่อนข้างง่ายที่จะแสดงใน Python shell

unittestห้องสมุด

ใน Python 2.7 หรือ 3:

import unittest

ใน Python 2.6 คุณสามารถติดตั้ง backport ของ 2.7 unittestไลบรารีเรียกunittest2และเพียงนามแฝงว่าunittest:

import unittest2 as unittest

ตัวอย่างการทดสอบ

ตอนนี้วางลงใน Python Shell ของคุณเพื่อทดสอบความปลอดภัยของ 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')

ทดสอบหนึ่งใช้assertRaisesเป็นตัวจัดการบริบทซึ่งทำให้แน่ใจว่าข้อผิดพลาดถูกจับและล้างอย่างถูกต้องขณะบันทึก

เราสามารถเขียนมันโดยไม่ต้องใช้ตัวจัดการบริบทดูทดสอบสอง อาร์กิวเมนต์แรกจะเป็นประเภทข้อผิดพลาดที่คุณคาดว่าจะเพิ่มอาร์กิวเมนต์ที่สองฟังก์ชั่นที่คุณกำลังทดสอบและ args ที่เหลืออยู่และ args คำหลักที่เหลือจะถูกส่งผ่านไปยังฟังก์ชั่นนั้น

ฉันคิดว่ามันง่ายกว่าอ่านและบำรุงรักษาได้มากขึ้นเพื่อใช้ตัวจัดการบริบท

ทำการทดสอบ

ในการรันการทดสอบ:

unittest.main(exit=False)

ใน Python 2.6 คุณอาจต้องมีสิ่งต่อไปนี้ :

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

และเทอร์มินัลของคุณควรส่งออกต่อไปนี้:

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

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

และเราจะเห็นว่าในขณะที่เราคาดว่าความพยายามที่จะเพิ่ม1และผลใน'1'TypeError


สำหรับผลลัพธ์ verbose เพิ่มเติมลองนี้:

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

56

รหัสของคุณควรเป็นไปตามรูปแบบนี้ (นี่เป็นการทดสอบรูปแบบโมดูลที่ไม่ซับซ้อนที่สุด):

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

บน Python <2.7 โครงสร้างนี้มีประโยชน์สำหรับการตรวจสอบค่าเฉพาะในข้อยกเว้นที่คาดไว้ ฟังก์ชั่น unittest assertRaisesจะตรวจสอบว่ามีข้อยกเว้นเกิดขึ้นหรือไม่


3
และเมธอด self.fail รับเพียงหนึ่งอาร์กิวเมนต์เท่านั้น
mdob

3
ดูเหมือนว่าจะซับซ้อนเกินกว่าที่จะทดสอบว่าฟังก์ชันมีข้อผิดพลาดหรือไม่ เนื่องจากข้อยกเว้นอื่น ๆ นอกเหนือจากข้อยกเว้นนั้นจะทำให้เกิดข้อผิดพลาดในการทดสอบและการไม่ยกเว้นข้อผิดพลาดจะทำให้การทดสอบล้มเหลวดูเหมือนว่าข้อแตกต่างเดียวคือถ้าคุณได้รับข้อยกเว้นอื่นด้วยassertRaisesคุณจะได้รับข้อผิดพลาดแทน FAIL
ยกเลิกการจัด

23

จาก: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/

ก่อนอื่นนี่คือฟังก์ชั่นที่เกี่ยวข้อง (ยัง dum: p) ในไฟล์ 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

นี่คือการทดสอบที่จะดำเนินการ (แทรกการทดสอบนี้เท่านั้น):

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

ตอนนี้เราพร้อมที่จะทดสอบฟังก์ชั่นของเราแล้ว! นี่คือสิ่งที่เกิดขึ้นเมื่อพยายามเรียกใช้การทดสอบ:

======================================================================
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 ถูกยกขึ้นมาใหม่และสร้างความล้มเหลวในการทดสอบ ปัญหาคือว่านี่เป็นพฤติกรรมที่เราต้องการ: s

เพื่อหลีกเลี่ยงข้อผิดพลาดนี้เพียงแค่เรียกใช้ฟังก์ชั่นโดยใช้แลมบ์ดาในการทดสอบการโทร:

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

ผลลัพธ์สุดท้าย:

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

OK

สมบูรณ์แบบ!

... และสำหรับฉันก็สมบูรณ์แบบเช่นกัน !!

ขอบคุณมากนาย Julien Lengrand-Lambert


ยืนยันการทดสอบนี้จะส่งกลับจริงเท็จบวก ที่เกิดขึ้นเพราะแลมบ์ดาภายใน 'assertRaises' เป็นหน่วยที่ทำให้เกิดข้อผิดพลาดประเภทไม่ใช่ฟังก์ชั่นการทดสอบ


10
แค่ทราบว่าคุณไม่ต้องการแลมบ์ดา บรรทัดself.assertRaises(TypeError, df.square_value(self.false_int))เรียกเมธอดและส่งคืนผลลัพธ์ สิ่งที่คุณต้องการคือการผ่านวิธีการและข้อโต้แย้งใด ๆ และปล่อยให้ unittest ที่จะเรียกมันว่า:self.assertRaises(TypeError, df.square_value, self.false_int)
โรมัน Kutlak

ขอบคุณมาก. ทำงานได้อย่างสมบูรณ์
Chandan Kumar

14

คุณสามารถสร้างของคุณเองcontextmanagerเพื่อตรวจสอบว่ามีการยกข้อยกเว้น

import contextlib

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

จากนั้นคุณสามารถใช้raisesสิ่งนี้:

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

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

หากคุณกำลังใช้pytestสิ่งนี้จะถูกนำไปใช้แล้ว คุณสามารถทำได้pytest.raises(Exception):

ตัวอย่าง:

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

และผลลัพธ์:

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
ขอบคุณที่โพสต์คำตอบที่ไม่ต้องใช้unittestโมดูล!
Sherwood Callaway

10

ฉันใช้doctest [1] เกือบทุกที่เพราะฉันชอบความจริงที่ว่าฉันบันทึกและทดสอบฟังก์ชั่นของฉันในเวลาเดียวกัน

ดูรหัสนี้:

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

หากคุณใส่ตัวอย่างนี้ในโมดูลและเรียกใช้จากบรรทัดคำสั่งทั้งสองกรณีทดสอบจะถูกประเมินและตรวจสอบ

[1] เอกสาร Python: 23.2 doctest - ทดสอบตัวอย่าง Python แบบโต้ตอบ


4
ฉันรักคำสอน แต่ฉันพบว่ามันเป็นอาหารเสริมมากกว่าที่จะมาแทนที่ไม่ได้
TimothyAWiseman

2
มีความเป็นไปได้ที่คำสอนจะมีความสุขน้อยลงเมื่อใช้การเปลี่ยนโครงสร้างอัตโนมัติหรือไม่ ฉันคิดว่าเครื่องมือการเปลี่ยนโครงสร้างที่ออกแบบมาสำหรับ python ควรตระหนักถึง docstrings ทุกคนสามารถแสดงความคิดเห็นจากประสบการณ์ของพวกเขา?
kdbanman

6

ฉันเพิ่งค้นพบว่าไลบรารี Mockจัดเตรียมเมธอด assertRaisesWithMessage () (ในคลาสย่อยของ unittest.TestCase) ซึ่งจะตรวจสอบไม่เพียงว่าข้อยกเว้นที่คาดไว้นั้นเพิ่มขึ้น

from testcase import TestCase

import mymod

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

น่าเสียดายที่มันไม่ได้ให้บริการอีกต่อไป .. แต่คำตอบข้างต้นของ @Art ( stackoverflow.com/a/3166985/1504046 ) ให้ผลลัพธ์เดียวกัน
Rmatt

6

มีคำตอบมากมายที่นี่ รหัสแสดงวิธีที่เราสามารถสร้างข้อยกเว้นวิธีที่เราสามารถใช้ข้อยกเว้นนั้นในวิธีการของเราและในที่สุดวิธีที่คุณสามารถตรวจสอบในการทดสอบหน่วยข้อยกเว้นที่ถูกต้องถูกยกขึ้น

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

คุณสามารถใช้ assertRaises จากโมดูล 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

ในขณะที่คำตอบทั้งหมดนั้นใช้ได้อย่างสมบูรณ์แบบฉันกำลังมองหาวิธีที่จะทดสอบว่าฟังก์ชั่นใดมีข้อยกเว้นโดยไม่ต้องอาศัยกรอบการทดสอบหน่วยและต้องเขียนคลาสทดสอบ

ฉันสิ้นสุดการเขียนต่อไปนี้:

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)

และมันล้มเหลวในบรรทัดที่ถูกต้อง:

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.