Python Mock ライブラリの助けを借りてテストしようとしているデコレータを持つ関数があります。mock.patchを使用して、関数を呼び出すだけのモック「バイパス」デコレータを備えた実際のデコレータ。実際のデコレータが関数をラップする前にパッチを適用する方法はわかりません。パッチとインポートのステートメントですが、成功していません。
デコレータは、関数の定義時に適用されます。ほとんどの機能では、これはモジュールがロードされるときです。 (他の関数で定義されている関数には、囲む関数が呼び出されるたびにデコレーターが適用されます。)
したがって、デコレータにモンキーパッチを適用する場合、必要なことは次のとおりです。
module.decorator = mymockdecorator
_デコレータを含むモジュールにそれを使用する関数も含まれている場合、それらは表示できるまでに既に装飾されており、おそらくS.O.Lです。
Pythonを最初に書いてからの変更を反映するように編集します。デコレータがfunctools.wraps()
を使用し、Pythonのバージョンが十分に新しい場合、 ___wrapped__
_ attritubeを使用して元の関数を掘り出し、それを再装飾できる場合がありますが、これは決して保証されるものではなく、置き換えるデコレーターだけが適用されるデコレーターではない場合があります。
ここでの回答のいくつかは、単一のテストインスタンスではなく、テストセッション全体のデコレータにパッチを適用することに注意してください。これは望ましくない場合があります。単一のテストでのみ持続するデコレータにパッチを適用する方法は次のとおりです。
望ましくないデコレータでテストするユニット:
# app/uut.py
from app.decorators import func_decor
@func_decor
def unit_to_be_tested():
# Do stuff
pass
デコレータモジュールから:
# app/decorators.py
def func_decor(func):
def inner(*args, **kwargs):
print "Do stuff we don't want in our test"
return func(*args, **kwargs)
return inner
テスト実行中にテストが収集されるまでに、望ましくないデコレータはテスト中のユニットに既に適用されています(インポート時に発生するため)。それを取り除くには、デコレータのモジュールのデコレータを手動で置き換えてから、UUTを含むモジュールを再インポートする必要があります。
テストモジュール:
# test_uut.py
from unittest import TestCase
from app import uut # Module with our thing to test
from app import decorators # Module with the decorator we need to replace
import imp # Library to help us reload our UUT module
from mock import patch
class TestUUT(TestCase):
def setUp(self):
# Do cleanup first so it is ready if an exception is raised
def kill_patches(): # Create a cleanup callback that undoes our patches
patch.stopall() # Stops all patches started with start()
imp.reload(uut) # Reload our UUT module which restores the original decorator
self.addCleanup(kill_patches) # We want to make sure this is run so we do this in addCleanup instead of tearDown
# Now patch the decorator where the decorator is being imported from
patch('app.decorators.func_decor', lambda x: x).start() # The lambda makes our decorator into a pass-thru. Also, don't forget to call start()
# HINT: if you're patching a decor with params use something like:
# lambda *x, **y: lambda f: f
imp.reload(uut) # Reloads the uut.py module which applies our patched decorator
クリーンアップコールバックkill_patchesは、元のデコレータを復元し、テストしていたユニットに再適用します。このように、私たちのパッチはセッション全体ではなく単一のテストを通してのみ持続します。これは他のパッチがどのように振る舞うべきであるかということです。また、クリーンアップはpatch.stopall()を呼び出すため、必要なsetUp()内の他のパッチを開始でき、それらはすべて1か所でクリーンアップされます。
この方法について理解する重要なことは、リロードが物事にどのように影響するかです。モジュールに時間がかかりすぎたり、インポート時に実行されるロジックがある場合は、ユニットの一部としてデコレーターをすくめてテストする必要がある場合があります。 :(うまくいけば、あなたのコードはそれより良く書かれています。そうですか?
パッチがテストセッション全体に適用されるかどうか気にしない場合、それを行う最も簡単な方法はテストファイルの一番上にあります:
# test_uut.py
from mock import patch
patch('app.decorators.func_decor', lambda x: x).start() # MUST BE BEFORE THE UUT GETS IMPORTED ANYWHERE!
from app import uut
UUTのローカルスコープではなく、デコレータを使用してファイルにパッチを適用し、デコレータを使用してユニットをインポートする前にパッチを開始してください。
興味深いことに、パッチを停止しても、既にインポートしたすべてのファイルにはデコレーターにパッチが適用されたままになります。これは、最初に行った状況の逆です。このメソッドは、後で実行されるテスト実行の他のファイルにパッチを適用することに注意してください-パッチ自体を宣言していなくても。
私がこの問題に最初に出くわしたとき、私は何時間も頭を悩ませていました。これを処理するはるかに簡単な方法を見つけました。
これは、ターゲットがそもそも装飾されていなかったように、デコレータを完全にバイパスします。
これは2つの部分に分けられます。次の記事を読むことをお勧めします。
http://alexmarandon.com/articles/python_mock_gotchas/
私が走り続けた2つの落とし穴:
1.)関数/モジュールをインポートする前に、デコレータをモックします。
デコレータと関数は、モジュールがロードされるときに定義されます。インポートの前にモックを作成しない場合、モックは無視されます。ロード後、奇妙なmock.patch.objectを実行する必要があり、これはさらにイライラさせられます。
2.)デコレータへの正しいパスをモックしていることを確認してください。
モックしているデコレータのパッチは、テストがデコレータをロードする方法ではなく、モジュールがデコレータをロードする方法に基づいていることに注意してください。そのため、インポートには常にフルパスを使用することをお勧めします。これにより、テストが非常に簡単になります。
手順:
1.)モック機能:
from functools import wraps
def mock_decorator(*args, **kwargs):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
return f(*args, **kwargs)
return decorated_function
return decorator
2.)デコレータのモック:
2a。)内のパス。
with mock.patch('path.to.my.decorator', mock_decorator):
from mymodule import myfunction
2b。)ファイルの先頭、またはTestCase.setUpでのパッチ
mock.patch('path.to.my.decorator', mock_decorator).start()
これらの方法のいずれかを使用すると、TestCaseまたはそのメソッド/テストケース内でいつでも関数をインポートできます。
from mymodule import myfunction
2.)mock.patchの副作用として別の関数を使用します。
これで、モックするデコレータごとにmock_decoratorを使用できます。各デコレータを個別にモックする必要があるため、見逃しているデコレータに注意してください。
次は私のために働いた:
それは魅力のように働いた。
たぶん、基本的にいくつかの構成変数をチェックしてテストモードが使用されることを意図しているかどうかを確認する別のデコレータをすべてのデコレータの定義に適用できます。
はいの場合、装飾しているデコレータを、何もしないダミーのデコレータに置き換えます。
それ以外の場合、このデコレータを通過させます。
これは少し奇妙に聞こえるかもしれませんが、sys.path
に自分自身のコピーをパッチし、テスト関数のスコープ内でインポートを実行できます。次のコードは概念を示しています。
from unittest.mock import patch
import sys
@patch('sys.modules', sys.modules.copy())
def testImport():
oldkeys = set(sys.modules.keys())
import MODULE
newkeys = set(sys.modules.keys())
print((newkeys)-(oldkeys))
oldkeys = set(sys.modules.keys())
testImport() -> ("MODULE") # Set contains MODULE
newkeys = set(sys.modules.keys())
print((newkeys)-(oldkeys)) -> set() # An empty set
MODULE
は、テストしているモジュールに置き換えることができます。 (これはPython 3.6でMODULE
をxml
に置き換えて動作します)
あなたの場合、デコレータ関数がモジュールpretty
にあり、装飾された関数がpresent
にあるとします。次に、モック機構を使用してpretty.decorator
にパッチを適用し、MODULE
with present
。次のようなものが動作するはずです(未テスト)。
クラスTestDecorator(unittest.TestCase):...
@patch(`pretty.decorator`, decorator)
@patch(`sys.path`, sys.path.copy())
def testFunction(self, decorator) :
import present
...
これは、テストモジュールの現在のsys.path
のコピーを使用して、各テスト関数に「クリーン」なsys.path
を提供することで機能します。このコピーは、モジュールが最初に解析されたときに作成され、すべてのテストで一貫したsys.path
が保証されます。
ただし、いくつかの影響があります。テストフレームワークが同じpythonセッションで複数のテストモジュールを実行する場合、MODULE
をインポートするすべてのテストモジュールは、ローカルにインポートするすべてのテストモジュールを破壊します。フレームワークが個別のpythonセッションで各テストモジュールを実行する場合、これは機能するはずです。同様に、インポートするテストモジュール内でMODULE
をグローバルにインポートすることはできません。 MODULE
ローカル。
ローカルインポートは、unittest.TestCase
のサブクラス内のテスト関数ごとに実行する必要があります。これをおそらくunittest.TestCase
サブクラスに直接適用して、クラス内のすべてのテスト関数でモジュールの特定のインポートを使用できるようにすることが可能です。
builtin
importsをいじるのは、MODULE
をsys
で置き換えることを見つけるでしょう。os
などは、sys.path
で既にあるので失敗しますコピーしてみてください。ここでのトリックは、ビルトインインポートを無効にしてPythonを呼び出すことです。python -X test.py
はそれを行うと思いますが、適切なフラグを忘れます(python --help
を参照)。 import builtins
、IIRCを使用してローカルにインポートされます。