Python 단위 테스트에서 메서드가 호출되었는지 확인


91

Python 단위 테스트에 다음 코드가 있다고 가정합니다.

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

aw.Clear()테스트의 두 번째 줄 에서 특정 메서드 (내 경우 )가 호출 되었다고 쉽게 주장 할 수있는 방법이 있습니까? 예를 들면 다음과 같습니다.

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

답변:


150

나는 이것을 위해 Mock (현재 py3.3 +에서 unittest.mock 임)을 사용합니다.

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 또는 Menace를 읽어 보세요 .


5
멋진 모의 모듈로 내 세계를 개별적으로 밝히는 +1.
Ron Cohen

@RonCohen : 예, 꽤 놀랍고 항상 좋아지고 있습니다. :)
Macke 2012

1
모의를 사용하는 것은 확실히 갈 방법이 있지만 단순히 : 존재하지 않는으로, 나는 assert_called_once를 사용하여 무리를 줄 것
FelixCQ

이후 버전에서는 제거되었습니다. 내 테스트는 여전히 그것을 사용하고 있습니다. :)
Macke

1
assert 메서드의 철자를 잘못 입력하면 실제로 물릴 수 있기 때문에 모의 객체에 autospec = True를 사용하는 것이 얼마나 도움이되는지 반복 할 가치가 있습니다.
rgilligan

30

예, Python 3.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를 수정하지 않아야하며 이는 거의 항상 사실입니다.


나는 내 파이썬이 녹슬 었다고 말했다. 비록 내 솔루션이 작동하는지 테스트했지만 :-) 나는 버전 2.5 이전에 파이썬을 내부화했다. 사실 나는 lib 호환성을 위해 2.3에서 동결해야했기 때문에 중요한 파이썬에 2.5를 사용한 적이 없었다. 귀하의 솔루션을 검토하면서 effbot.org/zone/python-with-statement.htm이 명확하고 명확한 설명으로 나타 났습니다 . 나는 내 접근 방식이 더 작아 보이며 중첩 된 "with"가 아닌 하나 이상의 로깅 지점을 원할 경우 적용하기 더 쉬울 수 있다고 겸손하게 제안합니다. 귀하의 특별한 혜택이 있는지 설명해 주시면 감사하겠습니다.
Andy Dent

@Andy : 부분적이기 때문에 답이 더 작습니다. 실제로 결과를 테스트하지 않고 테스트 후 원래 기능을 복원하지 않으므로 개체를 계속 사용할 수 있으며 모든 작업을 수행하기 위해 코드를 반복적으로 작성해야합니다. 다시 테스트를 작성할 때마다. 지원 코드 줄의 수는 중요하지 않습니다. 이 클래스는 독 스트링의 인라인이 아닌 자체 테스트 모듈에 들어갑니다. 실제 테스트에서는 한두 줄의 코드가 필요합니다.
Glenn Maynard

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에 호출 수를 추가했습니다.
Mark Heath

이것이 내가 제공 한 철저하고 독립적 인 솔루션에 대한 것입니까? 정말?
Glenn Maynard

@Glenn 저는 Python을 처음 접했습니다-아마도 당신의 것이 더 낫습니다-아직 모든 것을 이해하지 못합니다. 나는 나중에 그것을 시도하는 데 약간의 시간을 할애 할 것입니다.
Mark Heath

이것은 가장 간단하고 이해하기 쉬운 대답입니다. 정말 잘 했어요!
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가 호출되기를 원하지만이 접근 방식도 좋아합니다. 이것은 무슨 일이 일어나고 있는지 분명하게 만듭니다.
Mark Heath
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.