【发布时间】:2010-09-12 20:54:54
【问题描述】:
如何编写一个只有在函数没有抛出预期异常时才会失败的单元测试?
【问题讨论】:
标签: python unit-testing exception
如何编写一个只有在函数没有抛出预期异常时才会失败的单元测试?
【问题讨论】:
标签: python unit-testing exception
如果您使用的是 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()
【讨论】:
如果在此类中将 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
【讨论】:
对于 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()
【讨论】:
您可以使用上下文管理器运行错误函数,并使用assertRaisesMessage 断言它引发异常并带有特定消息
with self.assertRaisesMessage(SomeException,'Some error message e.g 404 Not Found'):
faulty_funtion()
【讨论】:
assertRaisesMessage 是仅 Django 的方法,而不是文档here 中所示的本机 Python Testcase 类方法,请编辑您的答案以澄清这一点。
由于我还没有看到任何关于如何使用上下文管理器检查我们是否在接受的列表中遇到特定异常的详细说明,或者其他异常详细信息,我将添加我的(在 python 3.8 上检查)。
如果我只是想检查函数是否正在引发例如TypeError,我会写:
with self.assertRaises(TypeError):
function_raising_some_exception(parameters)
如果我想检查函数是否引发了TypeError 或IndexError,我会写:
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])
【讨论】:
self.assertEqual(e.exception.args[0], "Values cannot be a generator. Use list(generator) instead.",) 的明确错误消息。
来自: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 是引发类型错误的单元,而不是测试的函数。
【讨论】:
self.assertRaises(TypeError, df.square_value(self.false_int)) 行调用方法并返回结果。您想要的是传递方法和任何参数并让单元测试调用它:self.assertRaises(TypeError, df.square_value, self.false_int)
这里有很多答案。代码展示了我们如何创建一个异常,我们如何在我们的方法中使用该异常,最后,您如何在单元测试中验证是否引发了正确的异常。
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()
【讨论】:
test_exception_message 真的很有帮助。需要获取返回的异常消息。谢谢你
虽然所有答案都很好,但我一直在寻找一种方法来测试函数是否引发异常,而无需依赖单元测试框架并且不必编写测试类。
我最终写了以下内容:
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
【讨论】:
您的代码应遵循此模式(这是一个单元测试模块样式测试):
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 assertRaises 只检查是否引发了异常。
【讨论】:
assertRaises 获得不同的异常,您将获得错误而不是失败。
从 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
在 Python 3.5 中,您必须将 context.exception 包裹在 str 中,否则您将得到 TypeError
self.assertTrue('This is broken' in str(context.exception))
【讨论】:
context.exception不给消息;它是一种类型。
import unittest2,你需要使用str(),即self.assertTrue('This is broken' in str(context.exception))。
使用单元测试模块中的TestCase.assertRaises(或TestCase.failUnlessRaises),例如:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)
【讨论】:
myfunc,您需要将它们作为参数添加到 assertRaises 调用中。请参阅达里尔·斯皮策的回答。
self.assertRaises(SomeCoolException, Constructor, arg1)
self.assertRaises(MyException, lambda: do_something(with=some, arguments))
如何测试 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 库的反向移植,称为 unittest2,并将其别名为 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 operator
self.assertRaises(TypeError, operator.add, 1, '1')
测试一使用assertRaises 作为上下文管理器,确保在记录时正确捕获和清理错误。
我们也可以在没有上下文管理器的情况下编写它,参见测试二。第一个参数是您希望引发的错误类型,第二个参数是您正在测试的函数,其余的参数和关键字参数将传递给该函数。
我认为仅使用上下文管理器就更加简单、可读和可维护。
运行测试:
unittest.main(exit=False)
在 Python 2.6 中,您可能会need the following:
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))
【讨论】:
我之前回答的代码可以简化为:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
如果一个函数接受参数,只需像这样将它们传递给 assertRaises:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
【讨论】:
2.7.15。如果self.assertRaises(ExpectedException, afunction, arg1, arg2) 中的afunction 是类初始化器,则需要将self 作为第一个参数传递,例如self.assertRaises(ExpectedException, Class, self, arg1, arg2)
您可以构建自己的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
【讨论】:
unittest 模块的答案!
您可以使用 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 assertRaises
self.assertRaises(Exception, test_class.raises_exception)
【讨论】:
我刚刚发现Mock library 提供了一个 assertRaisesWithMessage() 方法(在它的 unittest.TestCase 子类中),它不仅会检查是否引发了预期的异常,还会检查是否引发了预期的消息:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
'expected message',
mymod.myfunc)
【讨论】:
我几乎在所有地方都使用 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 documentation: 23.2 doctest -- Test interactive Python examples
【讨论】: