如何测试 Python 函数是否引发异常?

2025-01-06 08:32:00
admin
原创
94
摘要:问题描述:如何编写一个仅当函数未引发预期异常时才会失败的单元测试?解决方案 1:TestCase.assertRaises从模块中使用unittest,例如:import mymod class MyTestCase(unittest.TestCase): def test1(self): ...

问题描述:

如何编写一个仅当函数未引发预期异常时才会失败的单元测试?


解决方案 1:

TestCase.assertRaises从模块中使用unittest,例如:

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)

解决方案 2:

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

断言引发


Python 3.5中,你必须包装context.exception起来str,否则你会得到一个TypeError

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

解决方案 3:

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

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

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

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

解决方案 4:

如何测试 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 中,您可能需要以下内容:

unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))

你的终端应该输出以下内容:

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

OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>

我们看到,正如我们所料,尝试将 a1和 a相加'1',结果为 a TypeError


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

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

解决方案 5:

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

解决方案 6:

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

解决方案 7:

如果您使用pytest,则可以使用pytest.raises(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

或者您可以构建自己的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

解决方案 8:

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

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

with self.assertRaises(TypeError):
    function_raising_some_exception(parameters)

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

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

解决方案 9:

如果您使用的是 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()

解决方案 10:

我几乎所有地方都使用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 文档:23.2 doctest——测试交互式 Python 示例

解决方案 11:

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

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

解决方案 12:

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

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)

解决方案 13:

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

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

def test_raises(self):
    with self.assertRaises(RuntimeError):
        raise RuntimeError()

如果您想检查异常消息(请参阅下面的“使用上下文管理器的 assertRaisesRegex”选项仅检查其中的一部分):

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

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

解决方案 14:

您可以使用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 don’t use () when passing the method to assertRaises
    self.assertRaises(Exception, test_class.raises_exception)

解决方案 15:

对于 Django 用户,你可以使用上下文管理器来运行错误的函数,并使用以下方式断言它会引发异常并显示特定消息:assertRaisesMessage

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

解决方案 16:

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

解决方案 17:

如果将 stock_id 设置为此类中的整数,则会引发 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

解决方案 18:

最好使用 unittest 进行单元测试,但如果您想要快速修复,我们可以捕获异常,将其分配给变量,然后查看该变量是否是该异常类的实例。

让我们假设我们的错误函数抛出了一个 ValueError。

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

解决方案 19:

虽然所有答案都很好,但我正在寻找一种方法来测试函数是否引发异常,而不依赖于单元测试框架并且不必编写测试类。

我最终写下了以下内容:

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
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用