एक मॉक विधि के लिए लगातार कॉल को सम्मिलित करना


175

मॉक में एक सहायक assert_called_with()विधि है । हालाँकि, जहाँ तक मैं समझता हूँ कि यह केवल एक विधि के लिए अंतिम कॉल की जाँच करता है ।
यदि मेरे पास कोड है जो कि मॉकडाउन विधि को 3 बार क्रमिक रूप से कॉल करता है, तो हर बार अलग-अलग मापदंडों के साथ, मैं इन 3 कॉल्स को अपने विशिष्ट मापदंडों के साथ कैसे बता सकता हूं?

जवाबों:


179

assert_has_calls इस समस्या के लिए एक और दृष्टिकोण है।

डॉक्स से:

assert_has_calls (कॉल, any_order = गलत)

मुखर को निर्दिष्ट कॉल के साथ बुलाया गया है। कॉल के लिए mock_calls सूची की जाँच की जाती है।

अगर any_order गलत है (डिफ़ॉल्ट) तो कॉल अनुक्रमिक होना चाहिए। निर्दिष्ट कॉल से पहले या बाद में अतिरिक्त कॉल हो सकते हैं।

अगर any_order सही है तो कॉल किसी भी क्रम में हो सकते हैं, लेकिन उन्हें सभी mock_calls में दिखाई देना चाहिए।

उदाहरण:

>>> from unittest.mock import call, Mock
>>> mock = Mock(return_value=None)
>>> mock(1)
>>> mock(2)
>>> mock(3)
>>> mock(4)
>>> calls = [call(2), call(3)]
>>> mock.assert_has_calls(calls)
>>> calls = [call(4), call(2), call(3)]
>>> mock.assert_has_calls(calls, any_order=True)

स्रोत: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_has_calls


9
थोड़ा अजीब वे एक नया "कॉल" प्रकार जोड़ना चाहते थे जिसके लिए वे सिर्फ एक सूची या टपल का उपयोग कर सकते थे ...
jaapz

@jaapz यह उपवर्ग tuple: isinstance(mock.call(1), tuple)देता है True। उन्होंने कुछ तरीकों और विशेषताओं को भी जोड़ा।
jpmc26

13
मॉक के शुरुआती संस्करणों में एक सादे टपल का उपयोग किया गया था, लेकिन यह उपयोग करने के लिए अजीब हो जाता है। प्रत्येक फ़ंक्शन कॉल को (args, kwargs) का एक टपल प्राप्त होता है, इसलिए यह जांचने के लिए कि "foo (123)" को सही ढंग से कहा गया था, आपको "mock.call_args == ((123,), {}" का उच्चारण करने की आवश्यकता है, जो कि है "कॉल (१२३)" की तुलना में एक कौर
जोनाथन हार्टले

जब आप कॉल के प्रत्येक उदाहरण पर एक अलग रिटर्न मान की उम्मीद करते हैं तो आप क्या करते हैं?
कोडविथिप्राइड

2
@CodeWithPride के लिए यह अधिक काम लग रहा हैside_effect
Pigueiras

108

आमतौर पर, मैं कॉल के आदेश के बारे में परवाह नहीं करता, केवल यह कि वे हुए। उस मामले में, मैं assert_any_callएक दावे के साथ गठबंधन करता हूं call_count

>>> import mock
>>> m = mock.Mock()
>>> m(1)
<Mock name='mock()' id='37578160'>
>>> m(2)
<Mock name='mock()' id='37578160'>
>>> m(3)
<Mock name='mock()' id='37578160'>
>>> m.assert_any_call(1)
>>> m.assert_any_call(2)
>>> m.assert_any_call(3)
>>> assert 3 == m.call_count
>>> m.assert_any_call(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "[python path]\lib\site-packages\mock.py", line 891, in assert_any_call
    '%s call not found' % expected_string
AssertionError: mock(4) call not found

मुझे लगता है कि यह एक ही विधि में पारित कॉल की एक बड़ी सूची की तुलना में पढ़ने और समझने में आसान होने के लिए इस तरह से कर रहा है।

यदि आप आदेश के बारे में परवाह करते हैं या आप कई समान कॉलों की अपेक्षा करते हैं, assert_has_callsतो अधिक उपयुक्त हो सकता है।

संपादित करें

जब से मैंने यह उत्तर पोस्ट किया है, मैंने सामान्य रूप से परीक्षण करने के लिए अपने दृष्टिकोण को पुनर्विचार किया है। मुझे लगता है कि यह उल्लेखनीय है कि यदि आपका परीक्षण इस जटिल हो रहा है, तो आप अनुचित तरीके से परीक्षण कर सकते हैं या डिजाइन की समस्या हो सकती है। एक वस्तु उन्मुख डिजाइन में अंतर-वस्तु संचार के परीक्षण के लिए मोक्स डिजाइन किए गए हैं। यदि आपके डिजाइन को उन्मुख नहीं किया गया है (जैसा कि अधिक प्रक्रियात्मक या कार्यात्मक में), तो मॉक पूरी तरह से अनुपयुक्त हो सकता है। आपके पास विधि के अंदर बहुत अधिक हो सकता है, या आप आंतरिक विवरणों का परीक्षण कर सकते हैं जो सबसे अच्छा छोड़ दिया गया है। मैंने इस पद्धति में उल्लिखित रणनीति विकसित की जब मेरा कोड बहुत ऑब्जेक्ट ओरिएंटेड नहीं था, और मुझे विश्वास है कि मैं आंतरिक विवरणों का परीक्षण भी कर रहा था जो कि सबसे अच्छा छोड़ दिया गया था।


@ jpmc26 क्या आप अपने संपादन पर अधिक विस्तार कर सकते हैं? Left बेस्ट लेफ्ट अनमैक्ड ’से आपका क्या मतलब है? यदि कोई कॉल एक विधि के भीतर किया गया है तो आप और कैसे परीक्षण करेंगे
otgw

@ मीमो अक्सर, वास्तविक पद्धति को कहने देना बेहतर है। यदि अन्य विधि टूट गई है, तो यह परीक्षण को तोड़ सकता है, लेकिन बचने का मूल्य एक सरल, अधिक रख-रखाव परीक्षण के मूल्य से छोटा है। मॉक करने का सबसे अच्छा समय तब होता है जब दूसरी विधि के लिए बाहरी कॉल वह होता है जिसे आप परीक्षण करना चाहते हैं (आमतौर पर, इसका मतलब है कि किसी प्रकार का परिणाम इसमें पारित हो जाता है और परीक्षण के तहत कोड परिणाम नहीं देता है।) या दूसरी विधि। बाहरी निर्भरताएँ (डेटाबेस, वेबसाइटें) जिन्हें आप समाप्त करना चाहते हैं। (तकनीकी रूप से, अंतिम मामला एक स्टब का अधिक है, और मैं इस पर जोर देने में संकोच करूंगा।)
jpmc26

@ jpmc26 मॉकिंग उपयोगी है जब आप निर्भरता इंजेक्शन या रनटाइम रणनीति चुनने के कुछ अन्य तरीके से बचना चाहेंगे। जैसा कि आपने उल्लेख किया है, बाहरी सेवाओं को कॉल किए बिना तरीकों के आंतरिक तर्क का परीक्षण करना, और इससे भी महत्वपूर्ण बात, पर्यावरण के बारे में जागरूक किए बिना (कोई अच्छा कोड नहीं है do() if TEST_ENV=='prod' else dont()), आपके द्वारा सुझाए गए तरीके को आसानी से मॉक करके प्राप्त किया जाता है। इसका एक दुष्प्रभाव प्रति संस्करण परीक्षण बनाए रखने के लिए हो रहा है (Google खोज एपीआई v1 और v2 के बीच कोड परिवर्तन कहते हैं, आपका कोड संस्करण 1 कोई फर्क नहीं पड़ेगा) का परीक्षण करेगा
डैनियल डुबोव्स्की

@DanielDubovski आपके अधिकांश परीक्षण इनपुट / आउटपुट आधारित होने चाहिए। यह हमेशा संभव नहीं है, लेकिन अगर यह ज्यादातर समय संभव नहीं है, तो आपको संभवतः एक डिज़ाइन समस्या है। जब आपको कुछ मूल्य की आवश्यकता होती है जो सामान्य रूप से कोड के दूसरे टुकड़े से आती है और आप एक निर्भरता में कटौती करना चाहते हैं, तो एक स्टब आमतौर पर करेगा। मोक्स केवल तब आवश्यक होते हैं जब आपको यह सत्यापित करने की आवश्यकता होती है कि कुछ राज्य संशोधन फ़ंक्शन (शायद बिना रिटर्न वैल्यू के) कहा जाता है। (एक मॉक और स्टब के बीच का अंतर यह है कि आप स्टब के साथ कॉल पर जोर नहीं देते हैं।) मॉक्स का उपयोग करना जहां स्टब्स करेंगे, आपके परीक्षणों को कम बनाए रखने योग्य बनाता है।
jpmc26

@ jpmc26 बाहरी सेवा को आउटपुट का एक प्रकार नहीं कह रहा है? बेशक आप उस कोड को रिफलेक्टर कर सकते हैं जो मैसेज भेजता है जिसे भेजने के लिए कहा जाता है और कॉल परमर्स को बताने के बजाय इसका परीक्षण करते हैं, लेकिन IMHO, यह बहुत अधिक है। आप बाह्य API के कॉलिंग को फिर से डिज़ाइन करने का सुझाव कैसे देंगे? मैं सहमत हूं कि मॉकिंग को न्यूनतम रूप से काट दिया जाना चाहिए, सभी इम कह रहे हैं कि आप डेटा को परीक्षण करने के लिए बाहरी सेवाओं को भेजने के लिए चारों ओर जाते हैं ताकि यह सुनिश्चित हो सके कि तर्क अपेक्षा के अनुरूप व्यवहार कर रहा है।
डैनियल डुबोव्स्की

46

आप पिछले विधि कॉल के मापदंडों की तुलना करने के लिए Mock.call_args_listविशेषता का उपयोग कर सकते हैं । Mock.call_countविशेषता के साथ संयोजन के रूप में आपको पूर्ण नियंत्रण देना चाहिए।


9
assert_has_calls ()?
बावजा

5
assert_has_callsकेवल तभी चेक करता है कि अपेक्षित कॉल किया गया है, लेकिन ऐसा नहीं है कि केवल वही हैं।
ब्लू

17

मुझे हमेशा इसे बार-बार देखना पड़ता है, इसलिए यहां मेरा जवाब है।


एक ही वर्ग के विभिन्न ऑब्जेक्ट्स पर कई विधि कॉल करना

मान लें कि हमारे पास एक भारी शुल्क वर्ग है (जिसे हम मॉक करना चाहते हैं):

In [1]: class HeavyDuty(object):
   ...:     def __init__(self):
   ...:         import time
   ...:         time.sleep(2)  # <- Spends a lot of time here
   ...:     
   ...:     def do_work(self, arg1, arg2):
   ...:         print("Called with %r and %r" % (arg1, arg2))
   ...:  

यहाँ कुछ कोड है जो HeavyDutyकक्षा के दो उदाहरणों का उपयोग करता है :

In [2]: def heavy_work():
   ...:     hd1 = HeavyDuty()
   ...:     hd1.do_work(13, 17)
   ...:     hd2 = HeavyDuty()
   ...:     hd2.do_work(23, 29)
   ...:    


अब, यहाँ heavy_workफ़ंक्शन के लिए एक परीक्षण मामला है :

In [3]: from unittest.mock import patch, call
   ...: def test_heavy_work():
   ...:     expected_calls = [call.do_work(13, 17),call.do_work(23, 29)]
   ...:     
   ...:     with patch('__main__.HeavyDuty') as MockHeavyDuty:
   ...:         heavy_work()
   ...:         MockHeavyDuty.return_value.assert_has_calls(expected_calls)
   ...:  

हम HeavyDutyवर्ग के साथ मजाक कर रहे हैं MockHeavyDuty। प्रत्येक HeavyDutyउदाहरण से आने वाली विधि कॉलों का दावा करने के लिए MockHeavyDuty.return_value.assert_has_calls, हमें इसके बजाय संदर्भित करना होगा MockHeavyDuty.assert_has_calls। इसके अलावा, expected_callsहमें सूची में यह निर्दिष्ट करना होगा कि हम किस विधि के नाम पर कॉल करने के लिए रुचि रखते हैं। तो हमारी सूची कॉल के लिए बनी है call.do_work, जैसा कि बस के विपरीत है call

परीक्षण मामले का अभ्यास करने से हमें पता चलता है कि यह सफल है:

In [4]: print(test_heavy_work())
None


यदि हम heavy_workफ़ंक्शन को संशोधित करते हैं, तो परीक्षण विफल हो जाता है और एक उपयोगी त्रुटि संदेश उत्पन्न करता है:

In [5]: def heavy_work():
   ...:     hd1 = HeavyDuty()
   ...:     hd1.do_work(113, 117)  # <- call args are different
   ...:     hd2 = HeavyDuty()
   ...:     hd2.do_work(123, 129)  # <- call args are different
   ...:     

In [6]: print(test_heavy_work())
---------------------------------------------------------------------------
(traceback omitted for clarity)

AssertionError: Calls not found.
Expected: [call.do_work(13, 17), call.do_work(23, 29)]
Actual: [call.do_work(113, 117), call.do_work(123, 129)]


एक फ़ंक्शन में कई कॉल करने का दावा करना

उपरोक्त के साथ विपरीत करने के लिए, यहां एक उदाहरण दिया गया है जो दिखाता है कि फ़ंक्शन में कई कॉल को कैसे मॉक करें:

In [7]: def work_function(arg1, arg2):
   ...:     print("Called with args %r and %r" % (arg1, arg2))

In [8]: from unittest.mock import patch, call
   ...: def test_work_function():
   ...:     expected_calls = [call(13, 17), call(23, 29)]    
   ...:     with patch('__main__.work_function') as mock_work_function:
   ...:         work_function(13, 17)
   ...:         work_function(23, 29)
   ...:         mock_work_function.assert_has_calls(expected_calls)
   ...:    

In [9]: print(test_work_function())
None


दो मुख्य अंतर हैं। पहली बात यह है कि जब हम किसी फंक्शन का मखौल उड़ाते हैं तो हम इस्तेमाल callकरने के बजाय अपनी अपेक्षित कॉल का इस्तेमाल करते हैं call.some_method। दूसरा एक है कि हम कहते हैं assert_has_callsपर mock_work_functionबजाय पर की, mock_work_function.return_value

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.