私は、リモートサーバーに接続し、応答を待ってから、その応答に基づいてアクションを実行するプロジェクトに取り組んでいます。いくつかの異なる例外をキャッチし、どの例外がキャッチされるかによって動作が異なります。例えば:
_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
)
_
関数を直接実行すると、期待どおりにすべてが起こります。関数のtry
節に_raise requests.exceptions.ConnectionError
_を追加してテストしました。しかし、ユニットテストを実行すると、
_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アドインであるため、Python 3.3のFusion 360のパッケージバージョンを使用しています-私が知っている限り、バニラバージョン(つまり、独自のロールはしない)ですが、肯定的ではありません。
最小限の例でエラーを再現できました。
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())
OK、すべて問題ありません、両方のテストに合格します
問題はモックにあります。クラス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
config。しかし、問題は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
をモックしようとしたときに同じ問題に出くわしました(ソリューションを探しているときにこの投稿を見つけました)。
Serge 言ったことは正しい:
TL/DR:完全な要求パッケージをモックすると、except requests.exceptions.ConnectionError句がモックをキャッチしようとします。モックは実際にはBaseExceptionではないため、エラーが発生します。
私が想像できる唯一の解決策は、完全なリクエストをモックするのではなく、例外ではない部分のみをモックすることです。私は、モックする方法を見つけることができなかったことを認めなければなりませんこれ以外のすべてをモックします
私の解決策は、モジュール全体をモックしてから、例外のモック属性を実際のクラスの例外と等しくなるように設定し、事実上例外を「モック解除」することでした。たとえば、私の場合:
@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
paramは、指定されたものをすべて呼び出し、その呼び出しの戻り値でターゲットを置き換えます。 StubException
クラスをそのまま渡すと、クラスのコンストラクターが呼び出され、ターゲットオブジェクトにパッチが適用されます。例外はclassではなくinstanceです。私たちが欲しいもの。 lambda
でラップすることにより、意図したとおりにクラスを返します。
パッチが完了したら、stub_exception
オブジェクト(文字通りStubException
クラス)は、CustomError
であるかのように発生および捕捉できます。きちんとした!
sh パッケージをモックしようとしたときに、同様の問題に直面しました。 shは非常に便利ですが、すべてのメソッドと例外が動的に定義されているという事実により、それらを模擬することはより困難になります。 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:BaseExceptionを継承しないクラスをキャッチすることは許可されていません
struct.error
から発生したstruct.unpack
をキャッチしようとしたとき。
私のテストでこれを回避する最も簡単な方法は、モックのエラー属性の値を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のアプローチに似ていますが、テストにインポートを追加しなくても同じ動作をする必要がないので、確かに簡単です。私には、これがここの答えの推論の一般的なスレッドへの論理的な結論であるように思えます。