Pythonユニットテストでメソッドが呼び出されたことを表明します


94

Pythonユニットテストに次のコードがあるとします。

aw = aps.Request("nv1")
aw2 = aps.Request("nv2", aw)

aw.Clear()テストの2行目で特定のメソッド(私の場合)が呼び出されたことを表明する簡単な方法はありますか?たとえば、次のようなものはありますか?

#pseudocode:
assertMethodIsCalled(aw.Clear, lambda: aps.Request("nv2", aw))

回答:


153

私が使用モック(今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_withassert_called_onceまたはassert_called_wiht)を誤って入力した場合でも、テストが実行される可能性がありますautospec=true。詳細については、assert_called_once:Threat orMenaceを参照してください。


5
素晴らしいモックモジュールで私の世界を個別に啓発するための+1。
ロンコーエン

@RonCohen:ええ、それはかなり驚くべきことであり、常に良くなっています。:)
マッケ2012

1
モックを使用することは間違いなく進むべき道ですが、assert_called_onceを使用しないことをお勧めします。単に存在しません:)
FelixCQ 2013年

それ以降のバージョンでは削除されています。私のテストはまだそれを使用しています。:)
マッケ

1
アサーションメソッドのスペルを間違えると本当に噛み付く可能性があるため、モックオブジェクトにautospec = Trueを使用することがどれほど役立つかを繰り返す価値があります。
rgilligan 2017

31

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

14

何も内蔵されていることに気づいていません。実装は非常に簡単です。

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が錆びていると言いましたが、ソリューションが機能することを確認するためにテストしました:-)バージョン2.5より前にPythonを内部化しました。実際、libの互換性のために2.3でフリーズする必要があったため、重要なPythonに2.5を使用したことはありません。ソリューションを確認したところ、effbot.org / zone /python-with-statement.htmがわかりやすい説明であることがわかりました。ネストされた「with」ではなく、複数のロギングポイントが必要な場合は、私のアプローチが小さく見え、適用しやすいかもしれないことを謙虚に提案します。あなたの特別なメリットがあるかどうかを説明していただきたいと思います。
アンディデント2010年

@Andy:部分的であるため、答えは小さくなります。実際には結果をテストせず、テスト後に元の関数を復元しないため、オブジェクトを引き続き使用できます。すべてを実行するには、コードを繰り返し記述する必要があります。あなたがテストを書くたびにそれもまた。サポートコードの行数は重要ではありません。このクラスは、docstringのインラインではなく、独自のテストモジュールに入ります。これには、実際のテストで1行または2行のコードが必要です。
グレンメイナード

6

はい、概要を説明できますが、私の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)

いいね。methCallLoggerに呼び出しカウントを追加したので、それをアサートできます。
マークヒース

これは、私が提供した完全な自己完結型のソリューションを超えていますか?真剣に?
グレンメイナード

@Glenn私はPythonを初めて使用しますが、Pythonの方が優れているかもしれませんが、まだすべてを理解していません。後で試してみるのに少し時間をかけます。
マークヒース

これは、これまでで最も単純で理解しやすい答えです。本当にいい仕事です!
Matt Messersmith 2016年

4

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)

old_clearを呼び出してもらいたいのですが、私もこのアプローチが好きです。これにより、何が起こっているのかが明らかになります。
マークヒース
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.