首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >无法捕获模拟异常,因为它不能继承BaseException

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

Stack Overflow用户
提问于 2015-07-29 23:49:32
回答 6查看 23K关注 0票数 35

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

代码语言:javascript
复制
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

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

代码语言:javascript
复制
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子句来进行测试。但是当我运行我的单元测试时

代码语言:javascript
复制
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__钩子,但是我不知道在哪里可以找到它,或者同时我能做什么。我对unittest.mock.patch()也比较陌生,所以很有可能我也做错了什么。

这是一个Fusion360插件--所以它使用的是Fusion360的Python3.3打包版本--据我所知,这是一个普通版本(也就是说,他们没有自己的版本),但我对此并不乐观。

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2015-08-07 09:13:28

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

foo.py:

代码语言:javascript
复制
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"

没有嘲弄的测试:

代码语言:javascript
复制
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子句就无法捕获任何内容,我得到的错误与问题中的示例相同:

代码语言:javascript
复制
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())

立即给予:

代码语言:javascript
复制
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.ConnectionError进行真正的异常。但是问题仍然存在,子句试图捕获一个模拟

TL/DR:当您模拟完整的requests包时,except requests.exceptions.ConnectionError子句试图捕获一个模拟。因为模拟不是真正的BaseException,所以它会导致错误。

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

代码语言:javascript
复制
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方法。

票数 34
EN

Stack Overflow用户

发布于 2016-03-25 20:15:41

我在试图模仿sqlite3时遇到了同样的问题(并且在寻找解决方案时发现了这篇文章)。

塞尔日所说的是正确的:

TL/DR:当您模拟完整的请求包时,除了requests.exceptions.ConnectionError子句试图捕获一个模拟。因为模拟不是真正的BaseException,所以它会导致错误。 我能想象的唯一解决方案不是模拟完整的请求,而是只模拟不属于例外的部分。我必须承认,除了这句话之外,我找不出怎么说嘲弄一切

我的解决方案是模拟整个模块,然后为异常设置模拟属性,使其与实际类中的异常相等,从而有效地“非模拟”异常。例如,在我的例子中:

代码语言:javascript
复制
@mock.patch(MyClass.sqlite3)
def test_connect_fail(self, mock_sqlite3):
    mock_sqlite3.connect.side_effect = sqlite3.OperationalError()
    mock_sqlite3.OperationalError = sqlite3.OperationalError
    self.assertRaises(sqlite3.OperationalError, MyClass, self.db_filename)

对于requests,您可以像这样单独地分配异常:

代码语言:javascript
复制
    mock_requests.exceptions.ConnectionError = requests.exceptions.ConnectionError

或者对所有的requests异常都这样做:

代码语言:javascript
复制
    mock_requests.exceptions = requests.exceptions

我不知道这是否是“正确”的方法,但到目前为止,它似乎对我没有任何问题。

票数 6
EN

Stack Overflow用户

发布于 2016-09-03 00:17:08

对于那些需要模拟异常而不能简单修补head的人,这里有一个简单的解决方案,可以用空异常替换目标异常:

假设我们有一个要测试的通用单元,但我们必须对其进行模拟:

代码语言:javascript
复制
# app/foo_file.py
def test_me():
    try:
       foo()
       return "No foo error happened"
    except CustomError:  # <-- Mock me!
        return "The foo error was caught"

我们想要模拟CustomError,但是因为它是一个例外,所以如果我们试图像其他所有东西一样对它进行修补,就会遇到麻烦。通常,对patch的调用将目标替换为MagicMock,但在这里不起作用。嘲弄是巧妙的,但它们的行为不像例外。与其用模拟进行修补,不如给它一个存根异常。我们会在我们的测试文件中这样做。

代码语言:javascript
复制
# app/test_foo_file.py
from mock import patch


# A do-nothing exception we are going to replace CustomError with
class StubException(Exception):
    pass


# Now apply it to our test
@patch('app.foo_file.foo')
@patch('app.foo_file.CustomError', new_callable=lambda: StubException)
def test_foo(stub_exception, mock_foo):
    mock_foo.side_effect = stub_exception("Stub")  # Raise our stub to be caught by CustomError
    assert test_me() == "The error was caught"

# Success!

那么lambda是怎么回事?new_callable参数调用我们给它的任何东西,并用该调用的返回替换目标。如果我们直接传递我们的StubException类,它将调用类的构造函数,并用异常实例来修补目标对象,而不是我们想要的类。通过使用lambda包装它,它将返回我们想要的类。

完成修补后,stub_exception对象(实际上是我们的StubException类)可以引发并捕获,就好像它是CustomError一样。整洁!

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/31713054

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档