无法捕获模拟异常,因为它不继承 BaseException
Can't catch mocked exception because it doesn't inherit BaseException
我正在从事一个项目,该项目涉及连接到远程服务器、等待响应,然后根据该响应执行操作。我们捕获了几个不同的异常,并根据捕获到的异常采取不同的行为。例如:
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 - 据我所知这是一个原始版本(即他们不自己推出)但我'我对此并不肯定。
我可以用一个最小的例子重现错误:
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())
好的,一切正常,两个测试都通过
问题来自模拟。一旦 class 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.ConnectionError
强制一个真正的异常时我正在引发一个模拟。但问题仍然存在,except
子句试图捕获模拟。
TL/DR:当您模拟完整的 requests
包时,except requests.exceptions.ConnectionError
子句会尝试捕获模拟。由于模拟不是真正的 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
方法,但副作用除外。
我只是 运行 在尝试模拟 sqlite3
时遇到了同样的问题(并在寻找解决方案时发现了这个 post)。
说的是正确的:
TL/DR: as you mock the full requests package, the except requests.exceptions.ConnectionError clause tries to catch a mock. As the mock is not really a BaseException, it causes the error.
The only solution I can imagine is not to mock the full requests but only the parts that are not exceptions. I must admit I could not find how to say to mock mock everything except this
我的解决方案是模拟整个模块,然后将异常的模拟属性设置为等于真实 class 中的异常,有效地“取消模拟”异常。例如,在我的例子中:
@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
,您可以像这样单独分配例外:
mock_requests.exceptions.ConnectionError = requests.exceptions.ConnectionError
或者像这样对所有 requests
异常执行此操作:
mock_requests.exceptions = requests.exceptions
我不知道这是否是“正确”的做法,但到目前为止,它似乎对我没有任何问题。
对于我们这些需要模拟异常但不能通过简单地修补 head
来做到这一点的人来说,这里有一个简单的解决方案,可以用空异常替换目标异常:
假设我们有一个通用单元要测试,但我们必须模拟一个异常:
# 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
替换目标,但这在这里不起作用。模拟很漂亮,但它们的行为不像异常那样。与其用模拟打补丁,不如给它一个存根异常。我们将在我们的测试文件中这样做。
# 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
参数调用我们给它的任何内容,并用该调用的 return 替换目标。如果我们直接传递 StubException
class,它将调用 class 的构造函数并使用异常 instance 而不是 a 来修补我们的目标对象class 这不是我们想要的。通过用 lambda
包装它,它 return 就是我们想要的 class。
一旦我们的修补完成,stub_exception
对象(实际上就是我们的 StubException
class)就可以像 CustomError
一样被引发和捕获。整洁!
我在尝试模拟 sh package. While sh is very useful, the fact that all methods and exceptions are defined dynamically make it more difficult to mock them. So following the recommendation of the documentation 时遇到了类似的问题:
import unittest
from unittest.mock import Mock, patch
class MockSh(Mock):
# error codes are defined dynamically in sh
class ErrorReturnCode_32(BaseException):
pass
# could be any sh command
def mount(self, *args):
raise self.ErrorReturnCode_32
class MyTestCase(unittest.TestCase):
mock_sh = MockSh()
@patch('core.mount.sh', new=mock_sh)
def test_mount(self):
...
我在模拟 struct
时 运行 遇到了同样的问题。
我收到错误:
TypeError: catching classes that do not inherit from BaseException is not allowed
当试图捕捉从 struct.unpack
引发的 struct.error
时。
我发现在我的测试中解决这个问题的最简单方法是简单地将我的模拟中的错误属性的值设置为 Exception
。例如
我要测试的方法有这个基本模式:
def some_meth(self):
try:
struct.unpack(fmt, data)
except struct.error:
return False
return True
测试有这个基本模式。
@mock.patch('my_module.struct')
def test_some_meth(self, struct_mock):
'''Explain how some_func should work.'''
struct_mock.error = Exception
self.my_object.some_meth()
struct_mock.unpack.assert_called()
struct_mock.unpack.side_effect = struct_mock.error
self.assertFalse(self.my_object.some_meth()
这与@BillB 采取的方法类似,但它肯定更简单,因为我不需要将导入添加到我的测试中并且仍然得到相同的行为。在我看来,这似乎是此处答案中一般推理线索的合乎逻辑的结论。
使用 patch.object
部分模拟 class。
我的用例:
import unittest
from unittest import mock
import requests
def test_my_function(self):
response = mock.MagicMock()
response.raise_for_status.side_effect = requests.HTTPError
with mock.patch.object(requests, 'get', return_value=response):
my_function()
我正在从事一个项目,该项目涉及连接到远程服务器、等待响应,然后根据该响应执行操作。我们捕获了几个不同的异常,并根据捕获到的异常采取不同的行为。例如:
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 - 据我所知这是一个原始版本(即他们不自己推出)但我'我对此并不肯定。
我可以用一个最小的例子重现错误:
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())
好的,一切正常,两个测试都通过
问题来自模拟。一旦 class 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.ConnectionError
强制一个真正的异常时我正在引发一个模拟。但问题仍然存在,except
子句试图捕获模拟。
TL/DR:当您模拟完整的 requests
包时,except requests.exceptions.ConnectionError
子句会尝试捕获模拟。由于模拟不是真正的 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
方法,但副作用除外。
我只是 运行 在尝试模拟 sqlite3
时遇到了同样的问题(并在寻找解决方案时发现了这个 post)。
TL/DR: as you mock the full requests package, the except requests.exceptions.ConnectionError clause tries to catch a mock. As the mock is not really a BaseException, it causes the error.
The only solution I can imagine is not to mock the full requests but only the parts that are not exceptions. I must admit I could not find how to say to mock mock everything except this
我的解决方案是模拟整个模块,然后将异常的模拟属性设置为等于真实 class 中的异常,有效地“取消模拟”异常。例如,在我的例子中:
@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
,您可以像这样单独分配例外:
mock_requests.exceptions.ConnectionError = requests.exceptions.ConnectionError
或者像这样对所有 requests
异常执行此操作:
mock_requests.exceptions = requests.exceptions
我不知道这是否是“正确”的做法,但到目前为止,它似乎对我没有任何问题。
对于我们这些需要模拟异常但不能通过简单地修补 head
来做到这一点的人来说,这里有一个简单的解决方案,可以用空异常替换目标异常:
假设我们有一个通用单元要测试,但我们必须模拟一个异常:
# 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
替换目标,但这在这里不起作用。模拟很漂亮,但它们的行为不像异常那样。与其用模拟打补丁,不如给它一个存根异常。我们将在我们的测试文件中这样做。
# 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
参数调用我们给它的任何内容,并用该调用的 return 替换目标。如果我们直接传递 StubException
class,它将调用 class 的构造函数并使用异常 instance 而不是 a 来修补我们的目标对象class 这不是我们想要的。通过用 lambda
包装它,它 return 就是我们想要的 class。
一旦我们的修补完成,stub_exception
对象(实际上就是我们的 StubException
class)就可以像 CustomError
一样被引发和捕获。整洁!
我在尝试模拟 sh package. While sh is very useful, the fact that all methods and exceptions are defined dynamically make it more difficult to mock them. So following the recommendation of the documentation 时遇到了类似的问题:
import unittest
from unittest.mock import Mock, patch
class MockSh(Mock):
# error codes are defined dynamically in sh
class ErrorReturnCode_32(BaseException):
pass
# could be any sh command
def mount(self, *args):
raise self.ErrorReturnCode_32
class MyTestCase(unittest.TestCase):
mock_sh = MockSh()
@patch('core.mount.sh', new=mock_sh)
def test_mount(self):
...
我在模拟 struct
时 运行 遇到了同样的问题。
我收到错误:
TypeError: catching classes that do not inherit from BaseException is not allowed
当试图捕捉从 struct.unpack
引发的 struct.error
时。
我发现在我的测试中解决这个问题的最简单方法是简单地将我的模拟中的错误属性的值设置为 Exception
。例如
我要测试的方法有这个基本模式:
def some_meth(self):
try:
struct.unpack(fmt, data)
except struct.error:
return False
return True
测试有这个基本模式。
@mock.patch('my_module.struct')
def test_some_meth(self, struct_mock):
'''Explain how some_func should work.'''
struct_mock.error = Exception
self.my_object.some_meth()
struct_mock.unpack.assert_called()
struct_mock.unpack.side_effect = struct_mock.error
self.assertFalse(self.my_object.some_meth()
这与@BillB 采取的方法类似,但它肯定更简单,因为我不需要将导入添加到我的测试中并且仍然得到相同的行为。在我看来,这似乎是此处答案中一般推理线索的合乎逻辑的结论。
使用 patch.object
部分模拟 class。
我的用例:
import unittest
from unittest import mock
import requests
def test_my_function(self):
response = mock.MagicMock()
response.raise_for_status.side_effect = requests.HTTPError
with mock.patch.object(requests, 'get', return_value=response):
my_function()