モジュールA
の最上部にはimport B
が含まれています。ただし、テスト条件では、 モックB
in A
(モックA.B
)を使用し、B
のインポートを完全に控えたいと思います。
実際、B
はテスト環境に意図的にインストールされていません。
A
はテスト対象のユニットです。 A
のすべての機能をインポートする必要があります。 B
は、モックする必要があるモジュールです。しかし、最初にB
がimport A
である場合、A
内でB
をモックし、A
が実際のB
のインポートを停止する方法を教えてください。 ?
(Bがインストールされていない理由は、迅速なテストのためにpypyを使用しており、残念ながらBはpypyと互換性がないためです。)
これはどのように行うことができますか?
A
をインポートする前に_sys.modules['B']
_に割り当てて、必要なものを取得できます。
test.py:
_import sys
sys.modules['B'] = __import__('mock_B')
import A
print(A.B.__name__)
_
A.py:
_import B
_
注B.pyは存在しませんが、_test.py
_を実行するとエラーは返されず、print(A.B.__name__)
は_mock_B
_を出力します。 Bの実際の関数/変数/などをモックする_mock_B.py
_を作成する必要があります。または、Mock()を直接割り当てることができます。
test.py:
_import sys
sys.modules['B'] = Mock()
import A
_
組み込みの__import__
は、「mock」ライブラリを使用してモックを作成し、より詳細に制御できます。
# Store original __import__
orig_import = __import__
# This will be the B module
b_mock = mock.Mock()
def import_mock(name, *args):
if name == 'B':
return b_mock
return orig_import(name, *args)
with mock.patch('__builtin__.__import__', side_effect=import_mock):
import A
A
は次のようになります:
import B
def a():
return B.func()
A.a()
はb_mock.func()
を返しますが、これもモックできます。
b_mock.func.return_value = 'spam'
A.a() # returns 'spam'
Python 3:の注意:.0のchangelog で述べたように、__builtin__
はbuiltins
という名前になりました:
モジュールの名前を
__builtin__
からbuiltins
に変更しました(アンダースコアを削除し、「s」を追加)。
Python 3.の場合、__builtin__
をbuiltins
に置き換えれば、この回答のコードは正常に機能します。
インポートを模擬する方法(A.Bを模擬)?
モジュールAの上部にはインポートBが含まれています。
簡単です。インポートする前にsys.modulesのライブラリをモックするだけです。
if wrong_platform():
sys.modules['B'] = mock.MagicMock()
そして、A
がBのオブジェクトから返される特定のタイプのデータに依存しない限り:
import A
うまくいくはずです。
import A.B
をモックすることもできます:これはサブモジュールがある場合でも機能しますが、各モジュールをモックする必要があります。これがあるとしましょう:
from foo import This, That, andTheOtherThing
from foo.bar import Yada, YadaYada
from foo.baz import Blah, getBlah, boink
モックするには、上記を含むモジュールをインポートする前に以下を実行するだけです:
sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
(私の経験:1つのプラットフォームであるWindowsで動作する依存関係がありましたが、毎日のテストを実行するLinuxでは動作しませんでした。テストのために依存関係をモックする必要がありました。多くの相互作用を設定する必要はありませんでした。)
補遺:実際には、時間がかかった副作用をシミュレートする必要がありました。そのため、私はオブジェクトのメソッドが1秒間スリープする必要がありました。これは次のように機能します。
sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
# setup the side-effect:
from time import sleep
def sleep_one(*args):
sleep(1)
# this gives us the mock objects that will be used
from foo.bar import MyObject
my_instance = MyObject()
# mock the method!
my_instance.method_that_takes_time = mock.MagicMock(side_effect=sleep_one)
そして、実際のメソッドのように、コードの実行には時間がかかります。
私はここのパーティーに少し遅れていることに気づきましたが、これをmock
ライブラリーで自動化するやや狂った方法です:
(使用例はこちら)
import contextlib
import collections
import mock
import sys
def fake_module(**args):
return (collections.namedtuple('module', args.keys())(**args))
def get_patch_dict(dotted_module_path, module):
patch_dict = {}
module_splits = dotted_module_path.split('.')
# Add our module to the patch dict
patch_dict[dotted_module_path] = module
# We add the rest of the fake modules in backwards
while module_splits:
# This adds the next level up into the patch dict which is a fake
# module that points at the next level down
patch_dict['.'.join(module_splits[:-1])] = fake_module(
**{module_splits[-1]: patch_dict['.'.join(module_splits)]}
)
module_splits = module_splits[:-1]
return patch_dict
with mock.patch.dict(
sys.modules,
get_patch_dict('herp.derp', fake_module(foo='bar'))
):
import herp.derp
# prints bar
print herp.derp.foo
これがとてつもなく複雑である理由は、インポートが発生したときですpythonは基本的にこれを行います(たとえばfrom herp.derp import foo
を取る)
sys.modules['herp']
は存在しますか?それ以外の場合はインポートします。それでもImportError
でない場合sys.modules['herp.derp']
は存在しますか?それ以外の場合はインポートします。それでもImportError
でない場合sys.modules['herp.derp']
の属性foo
を取得します。その他ImportError
foo = sys.modules['herp.derp'].foo
このハッキングされたソリューションにはいくつかの欠点があります。他の何かがモジュールパス内の他の要素に依存している場合、この種の方法はねじ込みます。また、これはのみのようにインラインでインポートされるものに対して機能します
def foo():
import herp.derp
または
def foo():
__import__('herp.derp')
import ModuleB
を行う場合、組み込みメソッド__import__
を実際に呼び出しています:
ModuleB = __import__('ModuleB', globals(), locals(), [], -1)
__builtin__
モジュールをインポートしてこのメソッドを上書きし、__builtin__.__import__
methodのラッパーを作成できます。または、NullImporter
モジュールのimp
フックで遊ぶこともできます。例外をキャッチし、except
-ブロックでモジュール/クラスをモックします。
関連ドキュメントへのポインター:
これがお役に立てば幸いです。 [〜#〜] highly [〜#〜]pythonプログラミングと、a)本当に達成したいことをしっかりと理解すること、およびb)影響を完全に理解することが重要です。
Pythonでインポートをモックする素晴らしい方法を見つけました。それは、EricのZaadiソリューションが見つかりました ここ 私はDjangoアプリケーション内で使用しています。
SeatInterface
モデルクラスへのインターフェイスであるクラスSeat
があります。だから私のseat_interface
モジュール私はそのようなインポートを持っています:
from ..models import Seat
class SeatInterface(object):
(...)
SeatInterface
クラスとしてSeat
クラスを模擬したFakeSeat
クラスの分離テストを作成したかったのです。問題は-tu runがどのようにオフラインでテストするか、Djangoアプリケーションがダウンしていることです。以下のエラーがありました。
ImproperlyConfigured:BASE_DIRの設定を要求しましたが、設定は構成されていません。環境変数Django_SETTINGS_MODULEを定義するか、設定にアクセスする前にsettings.configure()を呼び出す必要があります。
0.078秒で1回のテストを実行
失敗(エラー= 1)
解決策は次のとおりです。
import unittest
from mock import MagicMock, patch
class FakeSeat(object):
pass
class TestSeatInterface(unittest.TestCase):
def setUp(self):
models_mock = MagicMock()
models_mock.Seat.return_value = FakeSeat
modules = {'app.app.models': models_mock}
patch.dict('sys.modules', modules).start()
def test1(self):
from app.app.models_interface.seat_interface import SeatInterface
そして、テストは魔法のように正常に実行されます:)
。
0.002秒で1回のテストを実施OK
アーロンホールの答えは私のために動作します。重要なことを1つだけ挙げておきます。
A.py
にある場合
from B.C.D import E
test.py
では、パスに沿ってすべてのモジュールをモックする必要があります。そうでない場合は、ImportError
を取得します
sys.moduels['B'] = mock.MagicMock()
sys.moduels['B.C'] = mock.MagicMock()
sys.moduels['B.C.D'] = mock.MagicMock()