如何编写只有在函数没有抛出预期异常时才会失败的单元测试呢?


当前回答

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

其他回答

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

断言


在Python 3.5中,必须对上下文进行包装。否则你会得到一个TypeError

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

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

从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是引发类型错误的单元,而不是被测试的函数。

由于我还没有看到任何关于如何检查我们是否在使用上下文管理器的接受列表中获得特定异常的详细解释,或其他异常细节,我将添加我的(在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])

有4个选项(你会在最后找到完整的例子):

assertRaises与上下文管理器

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

assertRaises一行程序

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

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

assertRaisesRegex与上下文管理器

第二个参数是正则表达式,是必选项。当您只想检查部分异常消息时,非常方便。

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

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