【问题标题】:Mocking custom exceptions in Python在 Python 中模拟自定义异常
【发布时间】:2023-03-22 17:40:01
【问题描述】:

大多数第三方 Python 库都会抛出自定义异常。其中许多异常都有其自身的依赖性和副作用。例如,考虑以下情况:

class ThirdPartyException(BaseException):
    def __init__(self):
        print("I do something arcane and expensive upon construction.")
        print("Maybe I have a bunch of arguments that can't be None, too.")

    def state(self) -> bool:
        # In real life, this could be True or False
        return True

此外,假设我必须处理这个异常,并且要做到这一点,我需要查看异常的状态。如果我想编写测试来检查处理此异常时的行为,我必须能够创建ThirdPartyException。但我可能连怎么做都想不通,更别提怎么便宜了。

如果这不是Exception 而我想编写测试,我会立即联系MagicMock。但我不知道如何使用MagicMock,但有一个例外。

如何测试以下代码中的错误处理案例,最好使用py.test

def error_causing_thing():
    raise ThirdPartyException() 

def handle_error_conditionally():
    try:
        error_causing_thing()
    exception ThirdPartyException as e:
        if state:
             return "Some non-error value"
        else:
             return "A different non-error value"

【问题讨论】:

  • 要明确的是,ThirdPartException 是您无法控制的东西,并且您希望error_causing_thing 提出具有特定状态值的东西吗?
  • 将异常 instance 作为属性或参数 side_effect 提供给模拟或 patch 调用。
  • @chepner,是的。如果state 是布尔值,我将编写两个测试用例来练习我的函数如何处理ThirdPartyException
  • @KlausD.,我想避免实际构建ThirdPartyException。我试图用spec=ThirdPartyException 模拟一个,但我得到一个错误,你不能提出任何不是BaseException 子类的东西。当我尝试修补作为模拟时,我得到了同样的错误。当我尝试将补丁作为我自己的替代异常时,它根本没有得到处理(因为它未被识别为ThirdPartyException 的实例)。
  • 最好弄清楚如何让error_causing_thing自然地引发​​适当的异常。

标签: python unit-testing exception-handling pytest


【解决方案1】:

我知道这是一个过时的问题,而且我没有使用 pytest,但我在 unittest 上遇到了类似的问题,只是找到了一个其他人可能会觉得有帮助的解决方案。我为我的自定义异常添加了一个patch,其中new 关键字的值是Exception 的子类(或者是Exception 类)的任何类:

import unittest
from unittest import TestCase
from unittest.mock import patch
# ... other imports required for these unit tests

class MockCustomException(Exception):
    def state(self):
        return self.__class__.state_return_value

class MyTestCase(TestCase):
    def setUp(self):
        custom_exception_patcher = patch(
            'path.to.CustomException',
            new=MockCustomException
        )
        custom_exception_patcher.start()                # start patcher
        self.addCleanup(custom_exception_patcher.stop)  # stop patch after test
    
    def test_when_state_true(self):
        MockCustomException.state_return_value = True
        self.assertEqual(handle_error_conditionally(), "Some non-error value")
    
    def test_when_state_false(self):
        MockCustomException.state_return_value = False
        self.assertEqual(handle_error_conditionally(), "A different non-error value")

此方法也可以在每个测试的基础上使用,将patch 用作装饰器或上下文管理器:

# ... imports, etc
class MockCustomExceptionStateTrue:
     def state(self):
         return True

@patch('path.to.CustomException', new=MockCustomExceptionStateTrue)
def test_with_decorator_patch(self):
     self.assertEqual(handle_error_conditionally(), "Some non-error value")

def test_with_context_manager(self):
    
    class MockCustomException:
         def state(self):
            return True
    
    with patch('path.to.CustomException', new=MockCustomException):
         self.assertEqual(handle_error_conditionally(), "Some non-error value")

希望这对某人有帮助!在简单浏览了 pytest 文档后,看起来monkeypatching 类似于 unittest 补丁功能

【讨论】:

    猜你喜欢
    • 2021-09-13
    • 1970-01-01
    • 1970-01-01
    • 2020-04-08
    • 2021-12-13
    • 1970-01-01
    • 1970-01-01
    • 2021-01-20
    • 1970-01-01
    相关资源
    最近更新 更多