インポートをモックする方法


144

モジュールの上部にA含まimport Bれています。しかし、試験条件の下で、私はしたいと思いモック BA(モックA.B)と完全に輸入を控えますB

実際、B意図的にテスト環境にインストールされることはありません。

Aテスト中のユニットです。Aすべての機能をインポートする必要があります。B私がモックする必要があるモジュールです。しかし、最初に行うことがインポートである場合、どうすればB内部をモックして実際のインポートAを停止できますか?ABAB

(Bがインストールされていない理由は、迅速なテストのためにpypyを使用しているためです。残念ながら、Bはまだpypyと互換性がありません。)

これはどのように行うことができますか?

回答:


134

sys.modules['B']インポートする前にを割り当ててA、必要なものを取得できます。

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ます。あなたはまだあなたが実際の関数/変数/などmock_B.pyを模擬する場所を作成する必要がありますB。または、Mock()直接割り当てることができます:

test.py

import sys
sys.modules['B'] = Mock()
import A

3
のようなMock一部の魔法の属性(__%s__)にパッチを適用しないことに注意してください__name__
reclosedev '12

7
@reclosedev- そのためのMagic Mockがあります
Jonathan

2
BインポートがImportErrorになるように、これをどのように元に戻しますか?試しましたsys.modules['B'] = Noneがうまくいかないようです。
audiodude

2
他の単体テストファイルがモックされたオブジェクトの影響を受けないように、テストの最後にこのモックされたインポートをどのようにリセットしますか?
Riya John

1
明確にするために、実際にインポートするように回答を編集してからmock呼び出すmock.Mock()
nmz787

28

ビルトイン__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のための注意: で述べたように3.0の変更履歴__builtin__今名前が付けられbuiltins

モジュールの名前を変更__builtin__しましたbuiltins(アンダースコアを削除し、 's'を追加します)。

あなたが交換した場合、この答えのコードは罰金を作品__builtin__によってbuiltinsPythonの3のために。


1
誰かがこの作品を確認しましたか?import_mockが呼び出されるのを見ていますがimport A、それがインポートするものは呼び出されていません。
Jonathon Reinhart 2016年

3
Pythonの3.4.3で、私が得るImportError: No module named '__builtin__'
ルーカスザシモン

インポートする必要があります__builtin__
Aidenhjj

1
@LucasCimonは、交換する__builtin__ことでbuiltinsのpython3(用docs.python.org/3/whatsnew/3.0.html?highlight=__builtin__
ルークマーリン

17

インポートをモックする方法(モックAB)?

モジュールAの上部にはインポートBが含まれています。

簡単です。インポートする前にsys.modulesのライブラリをモックするだけです。

if wrong_platform():
    sys.modules['B'] = mock.MagicMock()

そして、ABのオブジェクトから返される特定のタイプのデータに依存しない限り:

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()

(私の経験:Windowsの1つのプラットフォームで機能する依存関係がありましたが、毎日のテストを実行する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)

そして、実際のメソッドと同じように、コードの実行には少し時間がかかります。


7

私はここでのパーティーに少し遅れていることに気づきましたが、これは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

  1. ないsys.modules['herp']存在?それ以外の場合はインポートします。まだならImportError
  2. ないsys.modules['herp.derp']存在?それ以外の場合はインポートします。まだならImportError
  3. の属性fooを取得しますsys.modules['herp.derp']。そうしないとImportError
  4. foo = sys.modules['herp.derp'].foo

このハッキングされたソリューションにはいくつかの欠点があります。他に何かがモジュールパス内の他の要素に依存している場合、この種類の方法はそれをねじ込みます。また、これはインラインでインポートされているものに対してのみ機能します

def foo():
    import herp.derp

または

def foo():
    __import__('herp.derp')

7

アーロンホールの答えは私には有効です。ただ一つ重要なことを言いたいのですが

もしA.pyあなたがするなら

from B.C.D import E

次にtest.py、パスに沿ってすべてのモジュールをモックする必要があります。それ以外の場合は、ImportError

sys.modules['B'] = mock.MagicMock()
sys.modules['B.C'] = mock.MagicMock()
sys.modules['B.C.D'] = mock.MagicMock()

4

Pythonでインポートを模擬するための良い方法を見つけました。それはだエリックのZaadiのソリューションが見つけ、ここで私はちょうど私の内部で使用しているDjangoのアプリケーション。

モデルクラスSeatInterfaceへのインターフェースであるクラスを持っていますSeat。だから私のseat_interfaceモジュール内にそのようなインポートがあります:

from ..models import Seat

class SeatInterface(object):
    (...)

私はSeatInterfaceモックされたクラスを使って、クラスの分離されたテストを作成したいと思いSeatましたFakeSeat。問題は、Djangoアプリケーションがダウンしているときに、tu runがオフラインでテストを実行する方法でした。以下のエラーが発生しました:

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


3

あなたが行う場合、import ModuleBあなたは本当に組み込みメソッド__import__を次のように呼び出しています:

ModuleB = __import__('ModuleB', globals(), locals(), [], -1)

__builtin__モジュールをインポートしてこのメ​​ソッドを上書きし、メソッドのラッパーを作成できます__builtin__.__import__。またはNullImporterimpモジュールのフックで遊ぶこともできます。例外をキャッチし、モジュール/クラスをexcept-ブロックにモックします。

関連ドキュメントへのポインタ:

docs.python.org: __import__

impモジュールでインポート内部にアクセスする

これがお役に立てば幸いです。ことHIGHLYあなたはPythonプログラミングのより難解周囲に、あなたが本当に達成し、B)の影響を完全に理解したいのか)固体理解が重要であることをステップことadviced。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.