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


当前回答

如果你正在使用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

其他回答

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

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

如果一个函数有参数,就像这样把它们传递给assertRaises:

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

如何测试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))

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

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

如果在这个类中将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

你的代码应该遵循这个模式(这是一个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只检查是否引发了异常。