小编典典

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

python

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

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,但得到的错误大致相同。

我已经读过,所以我认为它在__del__某处一定是一个不好的选择,但我不确定在哪里寻找它或什至可以做些什么时间。我还比较陌生,unittest.mock.patch()所以很有可能在那做错了。

这是一个Fusion360插件,因此它使用的是Fusion 360的Python 3.3打包版本-
据我所知,它是一个原始版本(即,他们不会自己滚动),但我对此并不乐观。


阅读 213

收藏
2020-12-20

共1个答案

小编典典

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

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.ConnectionErrorin强制使用真正的异常时,我正在提出一个模拟。但是问题仍然在于
except子句试图捕获一个模拟

TL / DR:在模拟完整requests程序包时,该except requests.exceptions.ConnectionError子句尝试捕获模拟。由于模拟不是真正的a
BaseException,因此会导致错误。

我能想象的唯一解决方案不是模拟全部,requests而是仅模拟不是异常的部分。我必须承认, 除此以外, 我无法找到如何 模拟所有内容的
模拟方法,但在您的示例中,您只需要打补丁即可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方法,但有副作用。

2020-12-20