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

Dar*_*zer 695 python unit-testing exception-handling exception

如果一个函数没有抛出预期的异常,那么如何编写一个单元测试失败?

Moe*_*Moe 605

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

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)
Run Code Online (Sandbox Code Playgroud)

  • 请注意,要将参数传递给`myfunc`,您需要将它们作为参数添加到assertRaises调用中.请参阅Daryl Spitzer的答案. (45认同)
  • 要回答 BUInvent 的问题,如何做相反的事情:只需从测试中调用 `mymod.myfunc()` 即可。如果引发,该异常将导致测试失败。这是您从测试中调用的任何内容的默认行为。 (8认同)
  • 有没有办法与此相反?就像它失败只有函数确实抛出异常? (4认同)
  • 传递参数的另一种方法是 self.assertRaises(MyException, lambda: do_something(with=some,arguments))` (3认同)
  • 有没有办法允许多种异常类型? (2认同)
  • 测试类构造器的方法相同:self.assertRaises(SomeCoolException,Constructor,arg1) (2认同)

Art*_*Art 434

从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()
Run Code Online (Sandbox Code Playgroud)

http://docs.python.org/dev/library/unittest.html#unittest.TestCase.assertRaises


Python的3.5,你必须包装context.exceptionstr,否则,你会得到一个TypeError

self.assertTrue('This is broken' in str(context.exception))
Run Code Online (Sandbox Code Playgroud)

  • 我使用的是Python 2.7.10,上面的功能不起作用; `context.exception`没有给出消息; 这是一种类型. (6认同)
  • 同样在Python 2.7(至少在我的2.7.6中)使用`import unittest2`,你需要使用`str()`,即`self.assertTrue('str(context.exception)中的'这是'')``. (6认同)
  • 两件事:1.您可以使用assertIn而不是assertTrue.例如self.assertIn('This is broken',context.exception)2.在我的例子中,使用2.7.10,context.exception似乎是一个字符数组.使用str不起作用.我最终这样做了:''.join(context.exception)所以,放在一起:self.assertIn('this is broken',''.join(context.exception)) (3认同)

Dar*_*zer 287

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

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction)
Run Code Online (Sandbox Code Playgroud)

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

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)
Run Code Online (Sandbox Code Playgroud)

  • 第二部分讨论了在传递参数时该做什么真的很有帮助. (11认同)
  • 我正在使用`2.7.15`。如果 `self.assertRaises(ExpectedException, afunction, arg1, arg2)` 中的 `afunction` 是类初始值设定项,则需要传递 `self` 作为第一个参数,例如 `self.assertRaises(ExpectedException, Class, self, arg1, arg2)` (2认同)

Aar*_*all 120

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

如果函数没有抛出预期的异常,那么如何编写一个失败的测试呢?

简答:

将该self.assertRaises方法用作上下文管理器:

    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'
Run Code Online (Sandbox Code Playgroud)

示范

最佳实践方法很容易在Python shell中演示.

unittest

在Python 2.7或3中:

import unittest
Run Code Online (Sandbox Code Playgroud)

在Python 2.6中,您可以安装2.7的unittest库的backport ,称为unittest2,并将其别名为unittest:

import unittest2 as unittest
Run Code Online (Sandbox Code Playgroud)

示例测试

现在,将以下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')
Run Code Online (Sandbox Code Playgroud)

测试1 assertRaises用作上下文管理器,确保在记录时正确捕获和清除错误.

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

我认为只使用上下文管理器更简单,可读和可维护.

运行测试

要运行测试:

unittest.main(exit=False)
Run Code Online (Sandbox Code Playgroud)

在Python 2.6中,您可能需要以下内容:

unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Run Code Online (Sandbox Code Playgroud)

您的终端应输出以下内容:

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

OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>
Run Code Online (Sandbox Code Playgroud)

我们看到正如我们所期望的那样,尝试在a中添加a 1和a '1'结果TypeError.


要获得更详细的输出,请尝试以下方法:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Run Code Online (Sandbox Code Playgroud)


Dar*_*zer 52

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

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

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

  • 和方法self.fail只接受一个参数 (3认同)
  • 如果函数抛出异常,这似乎过于复杂.由于除了该异常之外的任何异常都会导致测试错误并且不会抛出异常将导致测试失败,看起来唯一的区别是如果使用`assertRaises`获得不同的异常,则会得到ERROR而不是FAIL. (3认同)

kri*_*iss 29

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

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

with self.assertRaises(TypeError):
    function_raising_some_exception(parameters)
Run Code Online (Sandbox Code Playgroud)

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

with self.assertRaises((TypeError,IndexError)):
    function_raising_some_exception(parameters)
Run Code Online (Sandbox Code Playgroud)

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

# 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])
Run Code Online (Sandbox Code Playgroud)

  • 由于类型检查和检查其他内容的建议,这个答案非常有用(哈!)。这帮助我使用 self.assertEqual(e.exception.args[0], "Values can not be a Generator. Use list(generator) 相反。",) 检查显式错误消息。 (3认同)

ife*_*aju 20

如果您使用的是 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()
Run Code Online (Sandbox Code Playgroud)

  • 在我的例子中,“msg”什么也不做。 (3认同)

mac*_*acm 18

来自: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
Run Code Online (Sandbox Code Playgroud)

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

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()
Run Code Online (Sandbox Code Playgroud)

我们现在准备测试我们的功能了!以下是尝试运行测试时发生的情况:

======================================================================
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)
Run Code Online (Sandbox Code Playgroud)

TypeError会引发actullay,并生成测试失败.问题是这正是我们想要的行为:s.

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

self.assertRaises(TypeError, lambda: df.square_value(self.false_int))
Run Code Online (Sandbox Code Playgroud)

最终输出:

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

OK
Run Code Online (Sandbox Code Playgroud)

完美!

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

Thansk先生很多Julien Lengrand-Lambert先生

  • 只是一个注释,你不需要lambda.行`self.assertRaises(TypeError,df.square_value(self.false_int))`调用该方法并返回结果.你想要的是传递方法和任何参数,让unittest调用它:`self.assertRaises(TypeError,df.square_value,self.false_int)` (9认同)

Pig*_*ras 12

您可以构建自己的,contextmanager以检查是否引发了异常.

import contextlib

@contextlib.contextmanager
def raises(exception):
    try:
        yield 
    except exception as e:
        assert True
    else:
        assert False
Run Code Online (Sandbox Code Playgroud)

然后你就可以这样使用raises:

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

with raises(Exception):
    raise Exception  # Calls assert True
Run Code Online (Sandbox Code Playgroud)

如果你正在使用pytest,这个东西已经实现了.你可以这样做pytest.raises(Exception):

例:

def test_div_zero():
    with pytest.raises(ZeroDivisionError):
        1/0
Run Code Online (Sandbox Code Playgroud)

结果如下:

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
Run Code Online (Sandbox Code Playgroud)

  • 感谢您发布不需要 `unittest` 模块的答案! (2认同)

pi.*_*pi. 11

我几乎到处都使用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()
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我喜欢doctest,但我觉得它补充而不是取代unittest. (4认同)
  • doctest不太可能在自动重构中发挥出色吗?我想为python*设计的重构工具应该*了解docstrings.谁能评论他们的经历? (2认同)

Gre*_*ill 10

看看模块的assertRaises方法unittest.


Dar*_*zer 6

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

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,它不再提供它了。但是@Art(http://stackoverflow.com/a/3166985/1504046)的上述答案给出了相同的结果 (2认同)

Ari*_*ury 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()
Run Code Online (Sandbox Code Playgroud)


Bru*_*lho 5

您可以使用单元测试模块中的assertRaises:

\n
import unittest\n\nclass TestClass():\n  def raises_exception(self):\n    raise Exception("test")\n\nclass MyTestCase(unittest.TestCase):\n  def test_if_method_raises_correct_exception(self):\n    test_class = TestClass()\n    # Note that you don\xe2\x80\x99t use () when passing the method to assertRaises\n    self.assertRaises(Exception, test_class.raises_exception)\n
Run Code Online (Sandbox Code Playgroud)\n


egv*_*gvo 5

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

使用上下文管理器进行断言

def test_raises(self):
    with self.assertRaises(RuntimeError):
        raise RuntimeError()
Run Code Online (Sandbox Code Playgroud)

如果您想检查异常消息(请参阅下面的“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")
Run Code Online (Sandbox Code Playgroud)

断言引发单行

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

def test_raises(self):
    self.assertRaises(RuntimeError, your_function)
Run Code Online (Sandbox Code Playgroud)

带有上下文管理器的assertRaisesRegex

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

def test_raises_regex(self):
    with self.assertRaisesRegex(RuntimeError, r'.* exception message'):
        raise RuntimeError('your exception message')
Run Code Online (Sandbox Code Playgroud)

断言RaisesRegex 一行

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

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

def test_raises_regex(self):
    self.assertRaisesRegex(RuntimeError, r'.* exception message', your_function)
Run Code Online (Sandbox Code Playgroud)

完整代码示例:

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()
Run Code Online (Sandbox Code Playgroud)

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