2015-04-29 7 views
6

मेरे पास एक साधारण ज्ञापन है जिसका उपयोग मैं महंगा नेटवर्क कॉल के आसपास कुछ समय बचाने के लिए कर रहा हूं। मोटे तौर पर, मेरे कोड इस तरह दिखता है:ज्ञापन कार्यों का परीक्षण कैसे किया जा सकता है?

# mem.py 
import functools 
import time 


def memoize(fn): 
    """ 
    Decorate a function so that it results are cached in memory. 

    >>> import random 
    >>> random.seed(0) 
    >>> f = lambda x: random.randint(0, 10) 
    >>> [f(1) for _ in range(10)] 
    [9, 8, 4, 2, 5, 4, 8, 3, 5, 6] 
    >>> [f(2) for _ in range(10)] 
    [9, 5, 3, 8, 6, 2, 10, 10, 8, 9] 
    >>> g = memoize(f) 
    >>> [g(1) for _ in range(10)] 
    [3, 3, 3, 3, 3, 3, 3, 3, 3, 3] 
    >>> [g(2) for _ in range(10)] 
    [8, 8, 8, 8, 8, 8, 8, 8, 8, 8] 
    """ 
    cache = {} 

    @functools.wraps(fn) 
    def wrapped(*args, **kwargs): 
     key = args, tuple(sorted(kwargs)) 
     try: 
      return cache[key] 
     except KeyError: 
      cache[key] = fn(*args, **kwargs) 
      return cache[key] 
    return wrapped 


def network_call(user_id): 
    time.sleep(1) 
    return 1 


@memoize 
def search(user_id): 
    response = network_call(user_id) 
    # do stuff to response 
    return response 

और मैं इस कोड है, जहां मैं network_call() के विभिन्न वापसी मान बाहर नकली यकीन है कि कुछ संशोधनों मैं search() काम में क्या अपेक्षा के अनुरूप बनाने के लिए के लिए परीक्षण किया है।

import mock 

import mem 


@mock.patch('mem.network_call') 
def test_search(mock_network_call): 
    mock_network_call.return_value = 2 
    assert mem.search(1) == 2 


@mock.patch('mem.network_call') 
def test_search_2(mock_network_call): 
    mock_network_call.return_value = 3 
    assert mem.search(1) == 3 

हालांकि, जब मैं इन परीक्षणों चलाने के लिए, मैं एक विफलता क्योंकि search() एक कैश्ड परिणाम देता है मिलता है।

CAESAR-BAUTISTA:~ caesarbautista$ py.test test_mem.py 
============================= test session starts ============================== 
platform darwin -- Python 2.7.8 -- py-1.4.26 -- pytest-2.6.4 
collected 2 items 

test_mem.py .F 

=================================== FAILURES =================================== 
________________________________ test_search_2 _________________________________ 

args = (<MagicMock name='network_call' id='4438999312'>,), keywargs = {} 
extra_args = [<MagicMock name='network_call' id='4438999312'>] 
entered_patchers = [<mock._patch object at 0x108913dd0>] 
exc_info = (<class '_pytest.assertion.reinterpret.AssertionError'>, AssertionError(u'assert 2 == 3\n + where 2 = <function search at 0x10893f848>(1)\n + where <function search at 0x10893f848> = mem.search',), <traceback object at 0x1089502d8>) 
patching = <mock._patch object at 0x108913dd0> 
arg = <MagicMock name='network_call' id='4438999312'> 

    @wraps(func) 
    def patched(*args, **keywargs): 
     # don't use a with here (backwards compatability with Python 2.4) 
     extra_args = [] 
     entered_patchers = [] 

     # can't use try...except...finally because of Python 2.4 
     # compatibility 
     exc_info = tuple() 
     try: 
      try: 
       for patching in patched.patchings: 
        arg = patching.__enter__() 
        entered_patchers.append(patching) 
        if patching.attribute_name is not None: 
         keywargs.update(arg) 
        elif patching.new is DEFAULT: 
         extra_args.append(arg) 

       args += tuple(extra_args) 
>    return func(*args, **keywargs) 

/opt/boxen/homebrew/lib/python2.7/site-packages/mock.py:1201: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

mock_network_call = <MagicMock name='network_call' id='4438999312'> 

    @mock.patch('mem.network_call') 
    def test_search_2(mock_network_call): 
     mock_network_call.return_value = 3 
>  assert mem.search(1) == 3 
E  assert 2 == 3 
E  + where 2 = <function search at 0x10893f848>(1) 
E  + where <function search at 0x10893f848> = mem.search 

test_mem.py:15: AssertionError 
====================== 1 failed, 1 passed in 0.03 seconds ====================== 

क्या ज्ञापन कार्यों का परीक्षण करने का कोई तरीका है? मैंने कुछ विकल्प माना है लेकिन उनमें से प्रत्येक को कमियां हैं।

एक समाधान memoize() नकल करना है। मैं ऐसा करने में अनिच्छुक हूं क्योंकि यह परीक्षणों के कार्यान्वयन विवरण को रिसाव करता है। सैद्धांतिक रूप से, मैं एक कार्यात्मक दृष्टिकोण से ध्यान देने, परीक्षण सहित, शेष प्रणाली के बिना कार्यों को याद और अनमोल करने में सक्षम होना चाहिए।

सजावट समारोह का पर्दाफाश करने के लिए कोड को फिर से लिखना एक और समाधान है। यही कारण है, मैं कुछ इस तरह कर सकता है:,

def _search(user_id): 
    return network_call(user_id) 
search = memoize(_search) 

बहरहाल, यह एक ही समस्याओं में के रूप में ऊपर चलाता है, हालांकि यह यकीनन बदतर है क्योंकि यह पुनरावर्ती कार्यों के लिए काम नहीं करेगा है।

+3

सुनिश्चित नहीं हैं कि मैं समझता हूँ। अलग-अलग रिटर्न मूल्यों के लिए आपके पास दो परीक्षण परीक्षण क्यों हैं? यदि आपके ज्ञात फ़ंक्शन के लिए "बाँध" मान वापस करने के लिए ठीक है (नेटवर्क से लाइव मान के समान नहीं), तो आपको दो मानों का परीक्षण नहीं करना चाहिए। यदि यह ठीक नहीं है, तो आपको अपने ज्ञापन को अधिक परिष्कृत बनाने की आवश्यकता है ताकि जब भी आवश्यक हो तो आप किसी भी तरह कैश को अमान्य कर सकते हैं। याद रखने में कोई उपयोग नहीं है यदि आपके पास यह जानने का कोई तरीका नहीं है कि ज्ञात मूल्य बनाम वास्तविक मूल्य प्राप्त करने के लिए ठीक है। – BrenBarn

+0

मुझे समझ में नहीं आता कि आपका प्रस्तावित समाधान क्यों काम नहीं करेगा। क्या आप अपने परीक्षणों में सिर्फ '_search' का उपयोग नहीं कर सकते? मेरी धारणा यह है कि जब आप नेटवर्क कॉल विभिन्न मान देता है, तो आप '_search' के व्यवहार का परीक्षण करना चाहते हैं, और ज्ञापन में रूचि नहीं रखते हैं। –

+0

एचएम, आपको पीछा करने में परेशानी है। परीक्षणों में 'network_call()' अनुकरण करने के लिए अलग-अलग रिटर्न मान होते हैं जिनके वापसी मान समान पैरामीटर के लिए भिन्न हो सकते हैं (यानी सर्वर पर मान के आधार पर)। उन्हें स्वतंत्र माना जाता है, इसलिए कैश को अमान्य करने की आवश्यकता नहीं होनी चाहिए। –

उत्तर

8

क्या यह वास्तव में फ़ंक्शन स्तर पर आपके ज्ञापन को परिभाषित करने के लिए वांछनीय है?

यह प्रभावी ढंग से ज्ञात डेटा को वैश्विक चर (बस फ़ंक्शन की तरह, जिसका दायरा साझा करता है) बनाता है।

संयोग से, यही कारण है कि आपको इसका परीक्षण करने में कठिनाई हो रही है!

तो, किसी ऑब्जेक्ट में इसे लपेटने के बारे में कैसे?

import functools 
import time 

def memoize(meth): 
    @functools.wraps(meth) 
    def wrapped(self, *args, **kwargs): 

     # Prepare and get reference to cache 
     attr = "_memo_{0}".format(meth.__name__) 
     if not hasattr(self, attr): 
      setattr(self, attr, {}) 
     cache = getattr(self, attr) 

     # Actual caching 
     key = args, tuple(sorted(kwargs)) 
     try: 
      return cache[key] 
     except KeyError: 
      cache[key] = meth(self, *args, **kwargs) 
      return cache[key] 

    return wrapped 

def network_call(user_id): 
    print "Was called with: %s" % user_id 
    return 1 

class NetworkEngine(object): 

    @memoize 
    def search(self, user_id): 
     return network_call(user_id) 


if __name__ == "__main__": 
    e = NetworkEngine() 
    for v in [1,1,2]: 
     e.search(v) 
    NetworkEngine().search(1) 

पैदावार:

Was called with: 1 
Was called with: 2 
Was called with: 1 

दूसरे शब्दों में, NetworkEngine के प्रत्येक उदाहरण के लिए अपने स्वयं के कैश हो जाता है। कैश साझा करने के लिए बस उसी का पुन: उपयोग करें, या नया कैश प्राप्त करने के लिए एक नया प्रारंभ करें।


अपने परीक्षण कोड में, आप उपयोग करेंगे:

@mock.patch('mem.network_call') 
def test_search(mock_network_call): 
    mock_network_call.return_value = 2 
    assert mem.NetworkEngine().search(1) == 2 
+1

+1 एक महान उत्तर के लिए, लेकिन यदि आप ओपी के मूल कोड का उपयोग करते हैं तो यह समझने में थोड़ा तेज़ हो सकता है। –

+1

@Asad यह एक अच्छा मुद्दा है! अपडेट किया गया:) –

0

आप प्रत्येक चिंता का परीक्षण करना चाहिए:

आप memoize से पता चला है, और मैं आपको लगता है कि परीक्षण किया है मान लेते हैं।

आपको लगता है कि network_call है, तो आपको अलगाव में परीक्षण करना चाहिए, याद नहीं किया जाना चाहिए।

अब आप दोनों को गठबंधन करना चाहते हैं, लेकिन संभावित रूप से यह लंबे नेटवर्क विलंबता से बचने के लिए अन्य कोड के लाभ के लिए होगा। हालांकि, अगर आप इस दूसरे कोड का परीक्षण करना चाहते हैं तो इसे 1 नेटवर्क कॉल भी नहीं करना चाहिए, इसलिए आपको पैरामीटर के रूप में फ़ंक्शन नाम देना पड़ सकता है।

संबंधित मुद्दे

 संबंधित मुद्दे