web-dev-qa-db-ja.com

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

モジュールAの最上部にはimport Bが含まれています。ただし、テスト条件では、 モックB in A(モックA.B)を使用し、Bのインポートを完全に控えたいと思います。

実際、Bはテスト環境に意図的にインストールされていません。

Aはテスト対象のユニットです。 Aのすべての機能をインポートする必要があります。 Bは、モックする必要があるモジュールです。しかし、最初にBがimport Aである場合、A内でBをモックし、Aが実際のBのインポートを停止する方法を教えてください。 ?

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

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

119
Jonathan

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
_
115
Rob Wouters

組み込みの__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に置き換えれば、この回答のコードは正常に機能します。

20
siebz0r

インポートを模擬する方法(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)

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

13
Aaron Hall

私はここのパーティーに少し遅れていることに気づきましたが、これを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. sys.modules['herp.derp']の属性fooを取得します。その他ImportError
  4. foo = sys.modules['herp.derp'].foo

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

def foo():
    import herp.derp

または

def foo():
    __import__('herp.derp')
7
Anthony Sottile

import ModuleBを行う場合、組み込みメソッド__import__を実際に呼び出しています:

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

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

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

docs.python.org:__import__

impモジュールを使用してインポート内部にアクセスする

これがお役に立てば幸いです。 [〜#〜] highly [〜#〜]pythonプログラミングと、a)本当に達成したいことをしっかりと理解すること、およびb)影響を完全に理解することが重要です。

3
Don Question

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

3
Hunter_71

アーロンホールの答えは私のために動作します。重要なことを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()
0
Qingyi Wu