如何测试 Python 函数是否引发异常?

How do you test that a Python function throws an exception?

提问人:Daryl Spitzer 提问时间:9/25/2008 最后编辑:Peter MortensenDaryl Spitzer 更新时间:1/8/2023 访问量:890806

问:

如何编写一个单元测试,只有在函数没有引发预期异常时才会失败?

python 单元测试 异常

评论


答:

985赞 Moe 9/25/2008 #1

使用 unittest 模块中的 TestCase.assertRaises(或 ),例如:TestCase.failUnlessRaises

import mymod

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

评论

26赞 BUInvent 7/27/2017
有没有办法做相反的事情?就像只有当函数抛出异常时才会失败一样?
154赞 cbron 2/22/2018
请注意,若要将参数传递给,需要将它们作为参数添加到 assertRaise 调用中。请参阅 Daryl Spitzer 的回答。myfunc
12赞 tschumann 6/8/2019
测试类构造函数也是如此:self.assertRaises(SomeCoolException, Constructor, arg1)
6赞 jemand771 3/5/2020
传递参数的另一种方法是self.assertRaises(MyException, lambda: do_something(with=some, arguments))
8赞 Jonathan Hartley 7/11/2020
要回答 BUInvent 的问题,如何反其道而行之:只需从您的测试中调用即可。如果引发,则该异常将无法通过测试。这是从测试中调用的任何内容的默认行为。mymod.myfunc()
67赞 Daryl Spitzer 9/25/2008 #2

您的代码应遵循以下模式(这是单元测试模块样式测试):

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赞 mdob 10/15/2012
self.fail 方法只接受一个参数
6赞 unflores 1/21/2015
对于测试函数是否引发异常来说,这似乎过于复杂。由于除该异常以外的任何异常都会使测试出错,而不抛出异常将使测试失败,因此似乎唯一的区别是,如果您遇到不同的异常,您将得到 ERROR 而不是 FAIL。assertRaises
517赞 Daryl Spitzer 9/25/2008 #3

我之前回答中的代码可以简化为:

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

如果一个函数接受参数,只需将它们传递给 assertRaise 中,如下所示:

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

评论

73赞 Sabyasachi 6/19/2018
第二个关于争论通过时该怎么做的片段真的很有帮助。
2赞 Minh Tran 8/24/2018
我正在使用 .如果 in 是类初始值设定项,则需要作为第一个参数传递,例如,2.7.15afunctionself.assertRaises(ExpectedException, afunction, arg1, arg2)selfself.assertRaises(ExpectedException, Class, self, arg1, arg2)
1赞 Alvaro Rodriguez Scelza 6/2/2021
如果 arg 必须为 parameter=value 类型,它也有效,例如:self.assertRaises(ExpectedException, afunction, arg1=val1)
0赞 Andy Fraley 9/30/2023
请注意,您也可以只执行 a: 并正常调用您的函数。with self.assertRaises(ExpectedException):
11赞 pi. 9/25/2008 #4

我几乎在任何地方都使用 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 10/6/2012
我喜欢 doctest,但我发现它是补充而不是取代 unittest。
3赞 kdbanman 6/16/2015
doctest 不太可能与自动重构很好地配合吗?我想为 python 设计的重构工具应该知道文档字符串。任何人都可以从他们的经验中发表评论吗?
6赞 Daryl Spitzer 10/28/2008 #5

我刚刚发现 Mock 库提供了一个 assertRaisesWithMessage() 方法(在其 unittest 中。TestCase 子类),它不仅会检查是否引发了预期的异常,而且还会检查它是否引发了预期的消息:

from testcase import TestCase

import mymod

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

评论

2赞 Rmatt 1/31/2013
不幸的是,它不再提供它了。但上面@Art(stackoverflow.com/a/3166985/1504046)的答案给出了相同的结果
677赞 Art 7/2/2010 #6

从 Python 2.7 开始,您可以使用上下文管理器来获取抛出的实际 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()

assertRaise


Python 3.5 中,你必须换行,否则你会得到一个context.exceptionstrTypeError

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

评论

8赞 LateCoder 2/26/2016
我正在使用 Python 2.7.10,上述方法不起作用; 不给出消息;它是一种类型。context.exception
9赞 Sohail Si 1/19/2017
同样在 Python 2.7 中(至少在我的 2.7.6 中)使用 ,您需要使用 ,即 .import unittest2str()self.assertTrue('This is broken' in str(context.exception))
4赞 blockcipher 2/7/2017
两件事:1.你可以使用 assertIn 而不是 assertTrue。例如 self.assertIn('This is broken', context.exception) 2.就我而言,使用 2.7.10,context.exception 似乎是一个字符数组。使用 str 不起作用。我最终这样做了: ''.join(context.exception) 所以,放在一起: self.assertIn('这坏了', ''.join(context.exception))
1赞 MadPhysicist 3/15/2017
您的方法因异常的 Traceback 堵塞测试控制台是否正常?如何防止这种情况发生?
1赞 zhihong 5/11/2017
后来我找到了另一种将消息获取为异常 str 的方法,它是 err = context.exception.message。然后也可以使用 self.assertEqual(err, 'This is broken') 来做测试。
5赞 Bruno Carvalho 10/21/2010 #7

您可以在 unittest 模块中使用 assertRaise:

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 don’t use () when passing the method to assertRaises
    self.assertRaises(Exception, test_class.raises_exception)
46赞 macm 2/6/2013 #8

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

首先,这是文件dum_function.py中相应的(仍然是 dum :p)函数:

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。

为避免此错误,只需在测试调用中使用 lambda 运行该函数:

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

最终输出:

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

OK

完善!

...对我来说也是完美的!

非常感谢 Julien Lengrand-Lambert 先生。


此测试断言实际上返回误报。发生这种情况是因为“assertRaises”中的 lambda 是引发类型错误的单元,而不是测试的函数。

评论

17赞 Roman Kutlak 8/8/2013
请注意,您不需要 lambda。该行调用该方法并返回结果。你想要的是传递方法和任何参数,并让 unittest 调用它:self.assertRaises(TypeError, df.square_value(self.false_int))self.assertRaises(TypeError, df.square_value, self.false_int)
1赞 jlengrand 3/18/2021
2021 年,我的一个朋友发现我的博客被用来回答这个问题。感谢您@macm的赞誉!
34赞 Pigueiras 5/21/2014 #9

如果你使用的是pytest,你可以使用:pytest.raises(Exception)

例:

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

结果:

$ 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

或者,您可以构建自己的异常来检查是否引发了异常。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

评论

4赞 Sherwood Callaway 1/6/2019
感谢您发布不需要该模块的答案!unittest
170赞 Russia Must Remove Putin 1/30/2015 #10

如何测试 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 中,您可以安装 2.7 库的向后移植,称为 unittest2,并将其别名为:unittestunittest

import unittest2 as unittest

示例测试

现在,将以下 Python 类型安全测试粘贴到 Python shell 中:

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

我们也可以在没有上下文管理器的情况下编写它,参见测试二。第一个参数是您期望引发的错误类型,第二个参数是您正在测试的函数,其余的参数和关键字参数将传递给该函数。

我认为仅使用上下文管理器要简单、可读和易于维护。

运行测试

要运行测试,请执行以下操作:

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


如需更详细的输出,请尝试以下操作:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
7赞 Arindam Roychowdhury 6/12/2019 #11

这里有很多答案。该代码展示了我们如何创建异常,如何在方法中使用该异常,最后,如何在单元测试中验证是否引发了正确的异常。

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

评论

1赞 Anum Sheraz 10/26/2021
最后的测试真的很有帮助。需要获取返回的异常消息。谢谢test_exception_message
-6赞 RUser4512 10/4/2019 #12

虽然所有的答案都很好,但我正在寻找一种方法来测试一个函数是否引发了异常,而不必依赖单元测试框架,也不必编写测试类。

我最终写了以下内容:

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
42赞 kriss 6/8/2020 #13

由于我还没有看到任何关于如何使用上下文管理器或其他异常详细信息检查我们是否在接受的异常列表中获得特定异常的详细说明,我将添加我的异常(在 Python 3.8 上检查)。

例如,如果我只想检查该函数是否正在引发,我会写:TypeError

with self.assertRaises(TypeError):
    function_raising_some_exception(parameters)

如果我想检查该函数是否引发 or ,我会写:TypeErrorIndexError

with self.assertRaises((TypeError,IndexError)):
    function_raising_some_exception(parameters)

如果我想了解有关引发的异常的更多详细信息,我可以在这样的上下文中捕获它:

# Here I catch any exception
with self.assertRaises(Exception) as e:
    function_raising_some_exception(parameters)

# Here I check actual exception type (but I could
# check anything else about that specific exception,
# like it's actual message or values stored in the exception)
self.assertTrue(type(e.exception) in [TypeError,MatrixIsSingular])

评论

4赞 Steve Piercy 9/3/2020
这个答案非常有用(哈!),因为类型检查和检查其他任何内容的建议。这帮助我检查了显式错误消息。self.assertEqual(e.exception.args[0], "Values cannot be a generator. Use list(generator) instead.",)
0赞 Florian Fasmeyer 11/20/2020
上下文管理错误是最好的。它们也允许您测试错误消息!
4赞 Denis Biwott 10/26/2020 #14

对于 Django 上的用户,您可以使用上下文管理器来运行错误的函数,并使用特定消息断言它引发异常assertRaisesMessage

with self.assertRaisesMessage(SomeException,'Some error message e.g 404 Not Found'):
    faulty_funtion()

评论

3赞 Matthew Barlowe 12/31/2020
assertRaisesMessage是仅限 Django 的方法,而不是原生的 Python 测试用例类方法,如文档所示,编辑您的答案以澄清这一点。
2赞 Stephan Schielke 1/12/2021 #15

对于 await/async aiounittest,有一个略有不同的模式:

https://aiounittest.readthedocs.io/en/latest/asynctestcase.html#aiounittest.AsyncTestCase

async def test_await_async_fail(self):
    with self.assertRaises(Exception) as e:
        await async_one()
2赞 EOD-STOCK-API 5/3/2021 #16

如果将此类中的 stock_id 设置为 Integer 将引发错误,这将引发 TypeError,如果发生这种情况,测试将通过,否则将失败

def set_string(prop, value):
   if not isinstance(value, str):
      raise TypeError("i told you i take strings only ")
   return value

class BuyVolume(ndb.Model):
    stock_id = ndb.StringProperty(validator=set_string)

from pytest import raises
buy_volume_instance: BuyVolume = BuyVolume()
with raises(TypeError):
  buy_volume_instance.stock_id = 25
19赞 ifedapo olarewaju 11/1/2021 #17

如果您使用的是 Python 3,为了断言异常及其消息,您可以在上下文管理器中使用并将消息作为关键字参数传递,如下所示:assertRaisesmsg

import unittest

def your_function():
    raise RuntimeError('your exception message')

class YourTestCase(unittest.TestCase):
    def test(self):
        with self.assertRaises(RuntimeError, msg='your exception message'):
            your_function()


if __name__ == '__main__':
    unittest.main()

评论

3赞 egvo 7/13/2022
msg就我而言,什么都不做。
0赞 ifedapo olarewaju 7/24/2022
@egvo你用哪个版本的 Python 尝试过这个?
0赞 egvo 7/25/2022
现在我用 3.10.5 检查了它,之前在我的家用笔记本电脑上用 3.9 检查了它。我换了字符串,它没有反应。 做对了。msgassertRaisesRegex
-1赞 Dr. Joy Singhal 2/5/2022 #18

使用 unittest 进行单元测试是首选,但如果您想要快速修复,我们可以捕获异常,将其分配给一个变量,然后查看该变量是否是该异常类的实例。

假设我们的 bad 函数抛出 ValueError。

    try:
      bad_function()
    except ValueError as e:
      assert isinstance(e, ValueError)
5赞 egvo 7/13/2022 #19

有 4 个选项(您将在最后找到完整的示例):

assert使用上下文管理器引发

def test_raises(self):
    with self.assertRaises(RuntimeError):
        raise RuntimeError()

如果要检查异常消息(请参阅下面的“assertRaisesRegex with context manager”选项,仅检查其中的一部分):

def test_raises(self):
    with self.assertRaises(RuntimeError) as error:
        raise RuntimeError("your exception message")
    self.assertEqual(str(error.exception), "your exception message")

assertRaise 单行

注意:这里不是函数调用,而是使用函数作为可调用函数(不带圆括号)。

def test_raises(self):
    self.assertRaises(RuntimeError, your_function)

assertRaisesRegex with context manager

第二个参数是正则表达式,是必需的。当您只想检查异常消息的一部分时,这很方便。

def test_raises_regex(self):
    with self.assertRaisesRegex(RuntimeError, r'.* exception message'):
        raise RuntimeError('your exception message')

assertRaisesRegex 单行

第二个参数是正则表达式,是必需的。当您只想检查异常消息的一部分时,这很方便。

注意:这里不是函数调用,而是使用函数作为可调用函数(不带圆括号)。

def test_raises_regex(self):
    self.assertRaisesRegex(RuntimeError, r'.* exception message', your_function)

完整代码示例:

import unittest

def your_function():
    raise RuntimeError('your exception message')

class YourTestCase(unittest.TestCase):

    def test_1_raises_context_manager(self):
        with self.assertRaises(RuntimeError):
            your_function()

    def test_1b_raises_context_manager_and_error_message(self):
        with self.assertRaises(RuntimeError) as error:
            your_function()
        self.assertEqual(str(error.exception), "your exception message")

    def test_2_raises_oneliner(self):
        self.assertRaises(RuntimeError, your_function)

    def test_3_raises_regex_context_manager(self):
        with self.assertRaisesRegex(RuntimeError, r'.* exception message'):
            your_function()

    def test_4_raises_regex_oneliner(self):
        self.assertRaisesRegex(RuntimeError, r'.* exception message', your_function)

if __name__ == '__main__':
    unittest.main()

虽然由开发人员决定遵循哪种风格,但我更喜欢使用上下文管理器的这两种方法。