Pythonユニットテストに次のコードがあるとします。
aw = aps.Request("nv1")
aw2 = aps.Request("nv2", aw)
aw.Clear()
テストの2行目で特定のメソッド(私の場合)が呼び出されたことを表明する簡単な方法はありますか?たとえば、次のようなものはありますか?
#pseudocode:
assertMethodIsCalled(aw.Clear, lambda: aps.Request("nv2", aw))
回答:
私が使用モック(今unittest.mockこのためpy3.3の+上を):
from mock import patch
from PyQt4 import Qt
@patch.object(Qt.QMessageBox, 'aboutQt')
def testShowAboutQt(self, mock):
self.win.actionAboutQt.trigger()
self.assertTrue(mock.called)
あなたの場合、次のようになります。
import mock
from mock import patch
def testClearWasCalled(self):
aw = aps.Request("nv1")
with patch.object(aw, 'Clear') as mock:
aw2 = aps.Request("nv2", aw)
mock.assert_called_with(42) # or mock.assert_called_once_with(42)
Mockは、オブジェクトやモジュールにパッチを適用する方法や、正しいものが呼び出されたことを確認する方法など、かなりの数の便利な機能をサポートしています。
買い手責任負担!(バイヤーは注意してください!)
を使用しない限り、Mockはこれがモックされた関数であると見なし、問題なく実行されるため、assert_called_with
(assert_called_once
またはassert_called_wiht
)を誤って入力した場合でも、テストが実行される可能性がありますautospec=true
。詳細については、assert_called_once:Threat orMenaceを参照してください。
Python3.3以降を使用している場合ははい。組み込みのunittest.mock
メソッドを使用して、呼び出されたメソッドをアサートできます。Python 2.6以降では、ローリングバックポートを使用しMock
ます。これは同じことです。
これがあなたの場合の簡単な例です:
from unittest.mock import MagicMock
aw = aps.Request("nv1")
aw.Clear = MagicMock()
aw2 = aps.Request("nv2", aw)
assert aw.Clear.called
何も内蔵されていることに気づいていません。実装は非常に簡単です。
class assertMethodIsCalled(object):
def __init__(self, obj, method):
self.obj = obj
self.method = method
def called(self, *args, **kwargs):
self.method_called = True
self.orig_method(*args, **kwargs)
def __enter__(self):
self.orig_method = getattr(self.obj, self.method)
setattr(self.obj, self.method, self.called)
self.method_called = False
def __exit__(self, exc_type, exc_value, traceback):
assert getattr(self.obj, self.method) == self.called,
"method %s was modified during assertMethodIsCalled" % self.method
setattr(self.obj, self.method, self.orig_method)
# If an exception was thrown within the block, we've already failed.
if traceback is None:
assert self.method_called,
"method %s of %s was not called" % (self.method, self.obj)
class test(object):
def a(self):
print "test"
def b(self):
self.a()
obj = test()
with assertMethodIsCalled(obj, "a"):
obj.b()
これには、オブジェクト自体がself.bを変更しないことが必要です。これはほとんどの場合trueです。
はい、概要を説明できますが、私のPythonは少し錆びていて、忙しくて詳細に説明できません。
基本的に、オリジナルを呼び出すメソッドにプロキシを配置する必要があります。例:
class fred(object):
def blog(self):
print "We Blog"
class methCallLogger(object):
def __init__(self, meth):
self.meth = meth
def __call__(self, code=None):
self.meth()
# would also log the fact that it invoked the method
#example
f = fred()
f.blog = methCallLogger(f.blog)
callableに関するこのStackOverflowの回答は、上記を理解するのに役立つ場合があります。
さらに詳細に:
答えは受け入れられましたが、Glennとの興味深い議論と数分間の空き時間のために、私は答えを拡大したいと思いました。
# helper class defined elsewhere
class methCallLogger(object):
def __init__(self, meth):
self.meth = meth
self.was_called = False
def __call__(self, code=None):
self.meth()
self.was_called = True
#example
class fred(object):
def blog(self):
print "We Blog"
f = fred()
g = fred()
f.blog = methCallLogger(f.blog)
g.blog = methCallLogger(g.blog)
f.blog()
assert(f.blog.was_called)
assert(not g.blog.was_called)
aw.Clear
手動で、またはpymoxなどのテストフレームワークを使用して、モックアウトできます。手動では、次のようなものを使用して行います。
class MyTest(TestCase):
def testClear():
old_clear = aw.Clear
clear_calls = 0
aw.Clear = lambda: clear_calls += 1
aps.Request('nv2', aw)
assert clear_calls == 1
aw.Clear = old_clear
pymoxを使用すると、次のようになります。
class MyTest(mox.MoxTestBase):
def testClear():
aw = self.m.CreateMock(aps.Request)
aw.Clear()
self.mox.ReplayAll()
aps.Request('nv2', aw)