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.