如何测试Python函数是否抛出异常?

如何编写一个仅在函数不抛出预期异常时失败的单元测试?

764669 次浏览

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

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

您的代码应该遵循以下模式(这是一个unittest模块样式测试):

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

在Python<2.7上,此构造可用于检查预期异常中的特定值。unittest函数assertRaises仅检查是否引发了异常。

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

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

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

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

我几乎在任何地方都使用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 doctestdoctest.testmod()

如果您将此示例放在一个模块中并从命令行运行它,则会评估和检查两个测试用例。

[1]Python留档:23.2 doctest测试交互式Python示例

我刚刚发现模拟库提供了一个assertRaisesWith Message()方法(在其unittest. TestCase子类中),它不仅会检查是否引发了预期的异常,还会检查它是否与预期的消息一起引发:

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

从Python 2.7开始,您可以使用上下文管理器来获取抛出的实际异常对象:

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


python3.5中,您必须将context.exception包装在str中,否则您将获得TypeError

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

您可以使用unittest模块中的assertRaises

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 assertRaisesself.assertRaises(Exception, test_class.raises_exception)

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

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

def square_value(a):"""Returns the square value of a."""try:out = a*aexcept TypeError:raise TypeError("Input should be a string:")
return out

以下是要执行的测试(仅插入此测试):

import dum_function as df # import function moduleimport unittestclass 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#---## TESTSdef test_square_value(self):# assertRaises(excClass, callableObj) prototypeself.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_valueself.assertRaises(TypeError, df.square_value(self.false_int))File "/home/jlengrand/Desktop/function.py", line 8, in square_valueraise 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是引发类型错误的单元,没有是测试函数。

如果你使用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/pythoncollected 1 items
tests/test_div_zero.py:6: test_div_zero PASSED

或者您可以构建自己的contextmanager来检查是否引发了异常。

import contextlib
@contextlib.contextmanagerdef raises(exception):try:yieldexcept exception as e:assert Trueelse:assert False

然后你可以像这样使用raises

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

如何测试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的unittest库的反向端口,称为单元测试2,并将其别名为unittest

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 operatorself.assertRaises(TypeError, operator.add, 1, '1')

测试一使用assertRaises作为上下文管理器,确保错误在记录时被正确捕获和清理。

我们也可以将其没有写入上下文管理器,参见测试2。第一个参数是您希望引发的错误类型,第二个参数是您正在测试的函数,其余的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


有关更多详细输出,请尝试以下操作:

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

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

import unittest
class DeviceException(Exception):def __init__(self, msg, code):self.msg = msgself.code = codedef __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()

虽然所有答案都很好,但我一直在寻找一种方法来测试函数是否引发异常,而无需依赖单元测试框架并编写测试类。

最后我写了以下内容:

def assert_error(e, x):try:e(x)except:returnraise 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_errorraise AssertionError()AssertionError

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

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

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

如果我想检查该函数是否正在提升TypeErrorIndexError,我会写:

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

如果我想要更多关于异常的细节,我可以在这样的上下文中捕获它:

# Here I catch any exceptionwith 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])

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

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

对于wait/asyncaiounittest,有一个稍微不同的模式:

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

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

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 raisesbuy_volume_instance: BuyVolume = BuyVolume()with raises(TypeError):buy_volume_instance.stock_id = 25

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

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

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

让我们假设我们的坏函数抛出一个ValueError。

    try:bad_function()except ValueError as e:assert isinstance(e, ValueError)

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

使用上下文管理器断言

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

如果您想检查异常消息(请参阅下面的"带有上下文管理器的assertRaisesRegex"选项以仅检查其中的一部分):

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

断言引发一行代码

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

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

带有上下文管理器的assertRaisesRegex

第二个参数是regex表达式,是强制性的。当您只想检查异常消息的一部分时,很方便。

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

assertRaisesRegex一行代码

第二个参数是regex表达式,是强制性的。当您只想检查异常消息的一部分时,很方便。

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

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

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