दावा करते हैं कि पायथन इकाई परीक्षण में एक विधि को बुलाया गया था


95

मान लीजिए कि पायथन इकाई परीक्षण में मेरे पास निम्नलिखित कोड है:

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

क्या यह दावा करने का एक आसान तरीका है कि एक विशेष विधि (मेरे मामले में aw.Clear()) को परीक्षण की दूसरी पंक्ति के दौरान कहा गया था? जैसे कुछ ऐसा है:

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

जवाबों:


153

मैं इसके लिए मॉक का उपयोग करता हूं (जो अब 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)

मॉक काफी उपयोगी सुविधाओं का समर्थन करता है, जिसमें किसी वस्तु या मॉड्यूल को पैच करने के तरीके, साथ ही यह जांचना कि सही चीज़ को बुलाया गया था, आदि।

कैवियट खाली! (सावधान ग्राहक!)

यदि आप गलत तरीके से assert_called_with( assert_called_onceया को assert_called_wiht) अपना परीक्षण अभी भी चला सकते हैं, क्योंकि मॉक यह सोचेगा कि यह एक नकली फ़ंक्शन है और खुशी के साथ, जब तक आप उपयोग नहीं करते हैं autospec=true। अधिक जानकारी के लिए assert_called_once पढ़ें : खतरा या खतरा


5
+1 अद्भुत मॉक मॉड्यूल के साथ अपनी दुनिया को समझ से दूर करने के लिए।
रॉन कोहेन

@ रॉन कोहेन: हाँ, यह बहुत आश्चर्यजनक है, और हर समय बेहतर हो रहा है। :)
मैके

1
मॉक का उपयोग करते समय निश्चित रूप से जाने का रास्ता है, मैं assert_called_once का उपयोग करने के खिलाफ सलाह दूंगा, बस मौजूद नहीं है :)
FelixCQ

इसे बाद के संस्करणों में हटा दिया गया है। मेरे परीक्षण अभी भी इसका उपयोग कर रहे हैं। :)
मैके

1
यह दोहराने लायक है कि ऑटोस्कोप का उपयोग करना कितना उपयोगी है = किसी भी नकली वस्तु के लिए सच है क्योंकि यह वास्तव में आपको काट सकता है यदि आप मुखर विधि को गलत करते हैं।
rgilligan

31

यदि आप Python 3.3+ का उपयोग कर रहे हैं तो हाँ। आप अंतर्निहित unittest.mockविधि का उपयोग कर सकते हैं जिसे कहा जाता है। पायथन 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()

इसके लिए आवश्यक है कि ऑब्जेक्ट स्वयं सेल्फ को संशोधित नहीं करेगा। जो कि लगभग हमेशा सत्य होता है।


मैंने कहा कि मेरा पायथन जंग खा गया है, हालांकि मैंने यह सुनिश्चित करने के लिए अपने समाधान का परीक्षण किया कि यह काम करता है :-) मैंने संस्करण 2.5 से पहले पायथन को आंतरिक कर दिया था, वास्तव में मैंने कभी भी किसी भी महत्वपूर्ण पायथन के लिए 2.5 का उपयोग नहीं किया था क्योंकि हमें कामेच्छा अनुकूलता के लिए 2.3 पर फ्रीज करना था। आपके समाधान की समीक्षा करने में मुझे एक अच्छा स्पष्ट विवरण के रूप में effbot.org/zone/python-with-statement.htm मिला । मैं विनम्रतापूर्वक सुझाव दूंगा कि मेरा दृष्टिकोण छोटा लग रहा है और यदि आप लॉगिंग के एक से अधिक बिंदुओं को चाहते हैं, तो इसे लागू करना आसान हो सकता है, बजाय कि नेस्टेड के साथ "। मैं वास्तव में आपको यह बताना चाहूंगा कि क्या आपके कोई विशेष लाभ हैं।
एंडी डेंट

@Andy: आपका उत्तर छोटा है क्योंकि यह आंशिक है: यह वास्तव में परिणामों का परीक्षण नहीं करता है, यह परीक्षण के बाद मूल फ़ंक्शन को पुनर्स्थापित नहीं करता है ताकि आप ऑब्जेक्ट का उपयोग करना जारी रख सकें, और आपको बार-बार सभी को करने के लिए कोड लिखना होगा हर बार जब आप एक परीक्षा लिखते हैं। समर्थन कोड की लाइनों की संख्या महत्वपूर्ण नहीं है; यह वर्ग अपने स्वयं के परीक्षण मॉड्यूल में जाता है, डॉकस्ट्रिंग में इनलाइन नहीं - यह वास्तविक परीक्षा में कोड की एक या दो पंक्तियों को लेता है।
ग्लेन मेनार्ड

6

हां, मैं आपको रूपरेखा दे सकता हूं लेकिन मेरा पायथन थोड़ा कठोर है और मैं विस्तार से समझाने में व्यस्त हूं।

मूल रूप से, आपको उस विधि में एक प्रॉक्सी लगाने की जरूरत है जो मूल को बुलाएगा, जैसे:

 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)

कॉल करने योग्य के बारे में यह स्टैकऑवरफ्लो उत्तर आपको ऊपर समझने में मदद कर सकता है।

विस्तृत रूप में:

हालांकि जवाब को स्वीकार किया गया था, ग्लेन के साथ दिलचस्प चर्चा और कुछ मिनटों के लिए स्वतंत्र होने के कारण, मैं अपने उत्तर पर विस्तार करना चाहता था:

# 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 पर कॉल काउंट जोड़ा है ताकि मैं इस पर मुखर हो सकूं।
मार्क हीथ

यह पूरी तरह से, आत्म निहित समाधान मैंने प्रदान किया है? गंभीरता से?
ग्लेन मेनार्ड

@Genn मैं अजगर के लिए बहुत नया हूँ - शायद आपका एक बेहतर है - मैं अभी तक यह सब नहीं समझता। मैं बाद में इसे आज़माने में थोड़ा समय लगाऊँगा।
मार्क हीथ

यह अब तक का सबसे सरल और सबसे आसान उत्तर है। वास्तव में अच्छा काम!
मैट मेस्मिथिथ

4

आप aw.Clearया तो मैन्युअल रूप से या एक परीक्षण ढांचे का उपयोग कर सकते हैं जैसे कि पॉक्सो । मैन्युअल रूप से, आप इसे कुछ इस तरह से उपयोग करेंगे:

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)

मुझे यह दृष्टिकोण भी पसंद है, हालांकि मैं अभी भी पुराने_क्लियर को कॉल करना चाहता हूं। यह स्पष्ट करता है कि क्या चल रहा है।
मार्क हीथ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.