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


当前回答

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

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

其他回答

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

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

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

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

如何测试Python函数是否抛出异常? 如何编写只有在函数不抛出时才会失败的测试呢 意料之中的例外?

简短的回答:

利用自我。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库的backport,称为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作为上下文管理器,以确保在记录错误的同时正确地捕获和清除错误。

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

正如我们所期望的那样,尝试在TypeError中添加一个1和一个'1'结果。


想要更详细的输出,试试这个:

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

你的代码应该遵循这个模式(这是一个unittest模块风格测试):

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只检查是否引发了异常。

如果你正在使用pytest,你可以使用pytest.raise (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

或者你可以构建自己的上下文管理器来检查异常是否被引发。

import contextlib

@contextlib.contextmanager
def raises(exception):
    try:
        yield
    except exception as e:
        assert True
    else:
        assert False

然后你可以像这样使用加薪:

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

with raises(Exception):
    raise Exception  # Calls assert True