python - 无法捕获模拟异常,因为它不继承 BaseException

标签 python exception-handling python-requests python-3.3 python-mock

我正在处理一个项目,该项目涉及连接到远程服务器、等待响应,然后根据该响应执行操作。我们捕获了几个不同的异常,并且根据捕获的异常而表现出不同的行为。例如:

def myMethod(address, timeout=20):
    try:
        response = requests.head(address, timeout=timeout)
    except requests.exceptions.Timeout:
        # do something special
    except requests.exceptions.ConnectionError:
        # do something special
    except requests.exceptions.HTTPError:
        # do something special
    else:
        if response.status_code != requests.codes.ok:
            # do something special
        return successfulConnection.SUCCESS

为了测试这一点,我们编写了如下测试

class TestMyMethod(unittest.TestCase):

    def test_good_connection(self):
        config = {
            'head.return_value': type('MockResponse', (), {'status_code': requests.codes.ok}),
            'codes.ok': requests.codes.ok
        }
        with mock.patch('path.to.my.package.requests', **config):
            self.assertEqual(
                mypackage.myMethod('some_address',
                mypackage.successfulConnection.SUCCESS
            )

    def test_bad_connection(self):
        config = {
            'head.side_effect': requests.exceptions.ConnectionError,
            'requests.exceptions.ConnectionError': requests.exceptions.ConnectionError
        }
        with mock.patch('path.to.my.package.requests', **config):
            self.assertEqual(
                mypackage.myMethod('some_address',
                mypackage.successfulConnection.FAILURE
            )

如果我直接运行该函数,一切都会按预期进行。我什至通过将 raise requests.exceptions.ConnectionError 添加到函数的 try 子句来进行测试。但是当我运行单元测试时,我得到了

ERROR: test_bad_connection (test.test_file.TestMyMethod)
----------------------------------------------------------------
Traceback (most recent call last):
  File "path/to/sourcefile", line ###, in myMethod
    respone = requests.head(address, timeout=timeout)
  File "path/to/unittest/mock", line 846, in __call__
    return _mock_self.mock_call(*args, **kwargs)
  File "path/to/unittest/mock", line 901, in _mock_call
    raise effect
my.package.requests.exceptions.ConnectionError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "Path/to/my/test", line ##, in test_bad_connection
    mypackage.myMethod('some_address',
  File "Path/to/package", line ##, in myMethod
    except requests.exceptions.ConnectionError:
TypeError: catching classes that do not inherit from BaseException is not allowed

我试图将我正在修补的异常更改为 BaseException,但我得到了或多或少相同的错误。

我已阅读 https://stackoverflow.com/a/18163759/3076272已经,所以我认为它一定是某个地方的错误 __del__ 钩子(Hook),但我不确定在哪里寻找它,或者我什至可以在此期间做什么。我对 unittest.mock.patch() 也比较陌生,所以我很可能在那里也做错了什么。

这是一个 Fusion360 插件,因此它使用的是 Fusion 360 的 Python 3.3 打包版本——据我所知,它是一个普通版本(即它们不会自己推出),但我对此并不肯定。

最佳答案

我可以用一个最小的例子重现错误:

foo.py:

class MyError(Exception):
    pass

class A:
    def inner(self):
        err = MyError("FOO")
        print(type(err))
        raise err
    def outer(self):
        try:
            self.inner()
        except MyError as err:
            print ("catched ", err)
        return "OK"

无需模拟即可测试:

class FooTest(unittest.TestCase):
    def test_inner(self):
        a = foo.A()
        self.assertRaises(foo.MyError, a.inner)
    def test_outer(self):
        a = foo.A()
        self.assertEquals("OK", a.outer())

好的,一切都很好,两个测试都通过了

问题来自于模拟。一旦模拟 MyError 类, expect 子句就无法捕获任何内容,并且我得到与问题示例相同的错误:

class FooTest(unittest.TestCase):
    def test_inner(self):
        a = foo.A()
        self.assertRaises(foo.MyError, a.inner)
    def test_outer(self):
        with unittest.mock.patch('foo.MyError'):
            a = exc2.A()
            self.assertEquals("OK", a.outer())

马上给:

ERROR: test_outer (__main__.FooTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "...\foo.py", line 11, in outer
    self.inner()
  File "...\foo.py", line 8, in inner
    raise err
TypeError: exceptions must derive from BaseException

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<pyshell#78>", line 8, in test_outer
  File "...\foo.py", line 12, in outer
    except MyError as err:
TypeError: catching classes that do not inherit from BaseException is not allowed

在这里我得到了你没有的第一个 TypeError,因为当你用 'requests.exceptions.ConnectionError': requests.exceptions 强制一个真正的异常时,我正在引发一个模拟。配置中的连接错误。但问题仍然存在,except 子句试图捕捉模拟

TL/DR:当你模拟完整的 requests 包时,except requests.exceptions.ConnectionError 子句试图捕捉一个模拟。由于 mock 不是真正的 BaseException,因此会导致错误。

我能想象的唯一解决方案不是模拟完整的 requests 而是只模拟不是异常的部分。我必须承认我找不到如何模拟 mock 除了 this 之外的所有内容,但在您的示例中,您只需要修补 requests.head。所以我认为这应该可行:

def test_bad_connection(self):
    with mock.patch('path.to.my.package.requests.head',
                    side_effect=requests.exceptions.ConnectionError):
        self.assertEqual(
            mypackage.myMethod('some_address',
            mypackage.successfulConnection.FAILURE
        )

即:仅修补 head 方法,将异常作为副作用。

关于python - 无法捕获模拟异常,因为它不继承 BaseException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31713054/

相关文章:

python - 在解析器中报告非 fatal error 的 Pythonic 方式是什么?

python - Microsoft Azure 应用程序网关阻止 python 的请求库

python - Pandas GroupBy计算满足一定条件的加权百分比

python - 解析 Pandas 中的列值

php - 将 PHP 错误发送到默认错误日志和用户定义的错误日志

ruby - 将异常处理作为 Ruby 中的常用方法

python - 如何使用Python的REQUESTS包发出post请求?

python请求将字符串作为文件发送

python - 如何设置 Atom 的 'styles.less' 文件以突出显示 Python 中的函数和方法调用?

python - PySpark 中可变列数的总和