プロジェクトに_some_package
_と_another_package
_の2つのパッケージがあるとします。
_# some_package/foo.py:
def bar():
print('hello')
_
_# another_package/function.py
from some_package.foo import bar
def call_bar():
# ... code ...
bar()
# ... code ...
_
_another_package.function.call_bar
_をモックアウト_some_package.foo.bar
_をテストしたいのは、回避したいネットワークI/Oがいくつかあるためです。
ここにテストがあります:
_# tests/test_bar.py
from another_package.function import call_bar
def test_bar(monkeypatch):
monkeypatch.setattr('some_package.foo.bar', lambda: print('patched'))
call_bar()
assert True
_
驚いたことに、これはhello
ではなくpatched
を出力します。テストにIPDBブレークポイントを設定して、このことをデバッグしようとしました。ブレークポイントの後に手動で_some_package.foo.bar
_をインポートしてbar()
を呼び出すと、patched
が表示されます。
私の実際のプロジェクトでは、状況はさらに興味深いものです。プロジェクトルートでpytestを呼び出すと、関数にパッチが適用されませんが、_tests/test_bar.py
_を引数として指定すると機能します。
私が理解している限り、それは_from some_package.foo import bar
_ステートメントと関係があります。モンキーパッチが発生する前に実行されている場合、パッチ適用は失敗します。ただし、上記の例の圧縮テストのセットアップでは、パッチは両方のケースで機能しません。
そして、なぜそれはIPDBで機能するのですかREPLブレークポイントにヒットした後?
名前付きインポートは、オブジェクトの新しい名前を作成します。その後、オブジェクトの古い名前を置き換える場合、新しい名前は影響を受けません。
モジュールをインポートし、module.bar
代わりに。これは常に現在のオブジェクトを使用します。
編集:
import module
def func_under_test():
module.foo()
def test_func():
monkeypatch.setattr(...)
func_under_test
Ronny's answer は機能しますが、アプリケーションコードを変更する必要があります。一般に、テストのためにこれを行うべきではありません。
代わりに、2番目のパッケージのオブジェクトに明示的にパッチを適用できます。これは nittestモジュールのドキュメント で言及されています。
monkeypatch.setattr('another_package.bar', lambda: print('patched'))
oPの質問に対する正しい答え:
monkeypatch.setattr('another_package.function.bar', lambda: print('patched'))