Python、単体テストおよびモックインポート
-
05-07-2019 - |
質問
私はプロジェクトに参加しており、大規模なコードベースのリファクタリングを開始しています。すぐに生じた問題の 1 つは、各ファイルが他の多くのファイルをインポートすることです。単体テストの作成を開始できるように、実際のコードを変更せずに単体テストでこれをエレガントな方法でモックするにはどうすればよいでしょうか?
例として:テストしたい関数を含むファイルは、Python コア ライブラリではなくソフトウェアの一部である 10 個の他のファイルをインポートします。
単体テストをできるだけ個別に実行できるようにしたいので、今のところ、インポートされているファイルに依存しない機能のみをテストします。
ご回答ありがとうございます。
最初は自分が何をしたいのかよくわかりませんでしたが、今ではわかったような気がします。
問題は、サードパーティの自動マジックにより、一部のインポートがアプリケーション全体が実行されている場合にのみ可能であることでした。したがって、sys.pathで指定したディレクトリにこれらのモジュール用のスタブをいくつか作成する必要がありました。
これで、モジュールが欠落しているという不満を抱くことなく、単体テスト ファイルにテストを書きたい関数を含むファイルをインポートできるようになりました。
解決
モジュールをインポートすると同時に、何もインポートしないようにする場合は、 __ import __
組み込み関数を置き換えることができます。
たとえば、このクラスを使用します:
class ImportWrapper(object):
def __init__(self, real_import):
self.real_import = real_import
def wrapper(self, wantedModules):
def inner(moduleName, *args, **kwargs):
if moduleName in wantedModules:
print "IMPORTING MODULE", moduleName
self.real_import(*args, **kwargs)
else:
print "NOT IMPORTING MODULE", moduleName
return inner
def mock_import(self, moduleName, wantedModules):
__builtins__.__import__ = self.wrapper(wantedModules)
try:
__import__(moduleName, globals(), locals(), [], -1)
finally:
__builtins__.__import__ = self.real_import
そして、テストコードで、 import myModule
を記述する代わりに、次のように記述します。
wrapper = ImportWrapper(__import__)
wrapper.mock_import('myModule', [])
mock_import
の2番目の引数は、内部モジュールにインポートする行うモジュール名のリストです。
この例をさらに変更して、たとえば単にインポートしないだけでなく、モジュールオブジェクトを独自のカスタムオブジェクトでモックするのではなく、必要な以外のモジュールをインポートします。
他のヒント
Python のインポート メカニズムを実際にいじりたい場合は、以下を参照してください。 ihooks
モジュール。の動作を変更するためのツールを提供します。 __import__
内蔵。しかし、あなたの質問からは、なぜこれを行う必要があるのかは明らかではありません。
"他の多くのファイルをインポートします&quot ;?カスタマイズされたコードベースの一部である他の多くのファイルをインポートしますか?または、Pythonディストリビューションの一部である他の多くのファイルをインポートしますか?または、他の多くのオープンソースプロジェクトファイルをインポートしますか?
インポートが機能しない場合は、「シンプル」になります PYTHONPAT
Hの問題。さまざまなプロジェクトディレクトリをすべて、テストに使用できる PYTHONPATH
に取得します。かなり複雑なパスがあり、Windowsではこのように管理します
@set Part1=c:\blah\blah\blah
@set Part2=c:\some\other\path
@set that=g:\shared\stuff
set PYTHONPATH=%part1%;%part2%;%that%
(a)物事の出所を把握し、(b)物事を移動するときに変更を管理できるように、パスの各部分を分離します。
PYTHONPATH
は順番に検索されるため、パスの順番を調整することで、使用するものを制御できます。
「すべて」があると、信頼の問題になります。
いずれか
- 何か(つまり、Pythonコードベース)を信頼し、インポートするだけです。
または
-
あなたは何か(つまり、あなた自身のコード)を信頼しておらず、あなたは
- 個別にテストして
- スタンドアロンテスト用にモックします。
Pythonライブラリをテストしますか?もしそうなら、あなたは多くの仕事を持っています。そうでない場合は、おそらく、実際にテストするものだけをモックアウトする必要があります。
単体テストの前に迅速かつ不潔な修正が必要な場合、難しい操作は必要ありません。
単体テストがテストするコードと同じファイルにある場合は、 globals()
辞書から不要なモジュールを削除するだけです。
これはかなり長い例です。内容を持つモジュール impp.py
があるとします:
value = 5
今、テストファイルに次のように記述できます。
>>> import impp
>>> print globals().keys()
>>> def printVal():
>>> print impp.value
['printVal', '__builtins__', '__file__', 'impp', '__name__', '__doc__']
impp
はインポートされたため、グローバルに含まれていることに注意してください。 impp
モジュールを使用する printVal
関数の呼び出しは引き続き機能します:
>>> printVal()
5
ただし、 globals()
から impp
キーを削除すると...
>>> del globals()['impp']
>>> print globals().keys()
['printVal', '__builtins__', '__file__', '__name__', '__doc__']
... printVal()
を呼び出そうとすると、次のメッセージが表示されます。
>>> printVal()
Traceback (most recent call last):
File "test_imp.py", line 13, in <module>
printVal()
File "test_imp.py", line 5, in printVal
print impp.value
NameError: global name 'impp' is not defined
...これはおそらくまさにあなたが達成しようとしているものです。
単体テストで使用するには、テストスイートを実行する直前にグローバルを削除できます。 __ main __
:
if __name__ == '__main__':
del globals()['impp']
unittest.main()
あなたのコメント上記では、納得させたいと言います。特定のモジュールがすでにインポートされているPython。これはまだ奇妙な目標のように見えますが、もしそれが本当にあなたがしたいことであれば、原則としてインポートメカニズムの背後でこっそり回り、 sys.modules
を変更することができます。パッケージのインポートでこれがどのように機能するかはわかりませんが、絶対インポートでは問題ないはずです。