web-dev-qa-db-ja.com

Pytest monkeypatchがインポートされた関数で機能しない

プロジェクトに_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ブレークポイントにヒットした後?

29
Glueon

名前付きインポートは、オブジェクトの新しい名前を作成します。その後、オブジェクトの古い名前を置き換える場合、新しい名前は影響を受けません。

モジュールをインポートし、module.bar代わりに。これは常に現在のオブジェクトを使用します。


編集:

import module 

def func_under_test():
  module.foo()

def test_func():
   monkeypatch.setattr(...)
   func_under_test
8
Ronny

Ronny's answer は機能しますが、アプリケーションコードを変更する必要があります。一般に、テストのためにこれを行うべきではありません。

代わりに、2番目のパッケージのオブジェクトに明示的にパッチを適用できます。これは nittestモジュールのドキュメント で言及されています。

monkeypatch.setattr('another_package.bar', lambda: print('patched'))
27
Alex

oPの質問に対する正しい答え:

monkeypatch.setattr('another_package.function.bar', lambda: print('patched'))
0
Felix Liu