python किसी फ़ंक्शन के पैच__कुल__




time mocking (3)

[संपादित करें]

शायद इस सवाल का सबसे दिलचस्प हिस्सा यह है कि मैं कुछ somefunction.__call__ क्यों नहीं पा सकता somefunction.__call__ ?

क्योंकि फ़ंक्शन __call__ के कोड का उपयोग नहीं करता है, लेकिन __call__ (एक विधि आवरण वस्तु) फ़ंक्शन का कोड का उपयोग करते हैं।

मुझे इसके बारे में कोई अच्छी तरह से लिखित दस्तावेज नहीं मिला है, लेकिन मैं इसे (Python2.7) साबित कर सकता हूं:

>>> def f():
...     return "f"
... 
>>> def g():
...     return "g"
... 
>>> f
<function f at 0x7f1576381848>
>>> f.__call__
<method-wrapper '__call__' of function object at 0x7f1576381848>
>>> g
<function g at 0x7f15763817d0>
>>> g.__call__
<method-wrapper '__call__' of function object at 0x7f15763817d0>

g कोड से f का कोड बदलें:

>>> f.func_code = g.func_code
>>> f()
'g'
>>> f.__call__()
'g'

बेशक f और f.__call__ संदर्भ नहीं बदले हैं:

>>> f
<function f at 0x7f1576381848>
>>> f.__call__
<method-wrapper '__call__' of function object at 0x7f1576381848>

मूल कार्यान्वयन पुनर्प्राप्त करें और इसके बजाय __call__ संदर्भ कॉपी करें:

>>> def f():
...     return "f"
... 
>>> f()
'f'
>>> f.__call__ = g.__call__
>>> f()
'f'
>>> f.__call__()
'g'

यह फ़ंक्शन पर कोई प्रभाव नहीं है f नोट: पायथन 3 में आपको __code__ बजाय __code__ उपयोग करना चाहिए।

मुझे उम्मीद है कि कोई मुझे इस व्यवहार के बारे में बताए दस्तावेज़ों को इंगित कर सकता है

आप के आसपास काम करने का एक तरीका है: utils आप परिभाषित कर सकते हैं

class Utcnow(object):
    def __call__(self):
        return datetime.datetime.utcnow()


utcnow = Utcnow()

और अब आपका पैच जादू की तरह काम कर सकता है

मूल उत्तर का पालन करें जो मैं अपने परीक्षणों को लागू करने का भी सबसे अच्छा तरीका मानता हूं।

मेरे पास अपना सोना नियम है : पैच सुरक्षित तरीके कभी नहीं । इस मामले में चीजें थोड़ा सहज होती हैं क्योंकि संरक्षित विधि परीक्षण के लिए शुरू की गई थी लेकिन मुझे नहीं पता कि क्यों

यहां असली समस्या यह है कि आप datetime.datetime.utcnow सीधे पैच नहीं कर सकते (सी एक्सटेंशन जितना आपने ऊपर टिप्पणी में लिखा था)। आप क्या कर सकते हैं मानक व्यवहार को लपेटें और utcnow फ़ंक्शन को ओवरराइड करके datetime पैच करें:

>>> with mock.patch("datetime.datetime", mock.Mock(wraps=datetime.datetime, utcnow=mock.Mock(return_value=3))):
...  print(datetime.datetime.utcnow())
... 
3

ठीक है, यह वास्तव में स्पष्ट और साफ नहीं है, लेकिन आप अपने स्वयं के फ़ंक्शन को इस तरह से पेश कर सकते हैं

def mock_utcnow(return_value):
    return mock.Mock(wraps=datetime.datetime, 
                     utcnow=mock.Mock(return_value=return_value)):

और अब

mock.patch("datetime.datetime", mock_utcnow(***))

ठीक उसी तरह करें जो आपको किसी अन्य परत के बिना और हर तरह के आयात के लिए चाहिए

एक और समाधान utils में आयात datetime और आयातित पैच ***.utils.datetime ; जो आपको अपने परीक्षणों को बदलने के बिना mock_utcnow() datetime संदर्भ कार्यान्वयन को बदलने के लिए कुछ स्वतंत्रता दे सकता है (इस मामले में mock_utcnow() को बदलने के लिए ख्याल रखना भी तर्क से mock_utcnow() है)।

मुझे परीक्षणों में वर्तमान समय-समय पर पैच करना होगा। मैं इस समाधान का उपयोग कर रहा हूं:

def _utcnow():
    return datetime.datetime.utcnow()


def utcnow():
    """A proxy which can be patched in tests.
    """
    # another level of indirection, because some modules import utcnow
    return _utcnow()

फिर मेरे परीक्षण में मैं ऐसा कुछ करता हूं:

    with mock.patch('***.utils._utcnow', return_value=***):
        ...

लेकिन आज एक विचार मेरे पास आया, कि मैं एक अतिरिक्त _utcnow होने के बजाय समारोह utcnow __call__ पैचिंग के द्वारा कार्यान्वयन सरल बना सकता हूँ

यह मेरे लिए काम नहीं करता है:

    from ***.utils import utcnow
    with mock.patch.object(utcnow, '__call__', return_value=***):
        ...

यह खूबसूरत कैसे करना है?


प्रश्न पर टिप्पणी के रूप में, datetime.datetime सी में लिखा गया है, नकली कक्षा पर विशेषताओं को नहीं बदल सकता है (देखें मध्यांतर Datetime.today नेड Batchelder द्वारा)। इसके बजाय आप फ्रीजगुन का उपयोग कर सकते हैं

$ pip install freezegun

यहां एक उदाहरण है:

import datetime

from freezegun import freeze_time

def my_now():
    return datetime.datetime.utcnow()


@freeze_time('2000-01-01 12:00:01')
def test_freezegun():
    assert my_now() == datetime.datetime(2000, 1, 1, 12, 00, 1)

जैसा कि आप उल्लेख करते हैं, प्रत्येक मॉड्यूल को datetime - datetime आयात करने और उनके पैच को ट्रैक करने का एक विकल्प होता है। यह सार में है कि फ्रीजगुन क्या करता है यह एक ऑब्जेक्ट sys.modules का sys.modules लेता है, यह sys.modules लिए कि sys.modules माध्यम से sys.modules आयात किया गया है और हर उदाहरण को बदलता है। मुझे लगता है कि यह तर्कसंगत है कि आप एक समारोह में यह सुंदर ढंग से कर सकते हैं।


जब आप फ़ंक्शन के पथ __call__ करते हैं, तो आप उस उदाहरण के __call__ विशेषता को सेट कर रहे हैं। पायथन वास्तव में कक्षा पर परिभाषित __call__ विधि कहता है।

उदाहरण के लिए:

>>> class A(object):
...     def __call__(self):
...         print 'a'
...
>>> a = A()
>>> a()
a
>>> def b(): print 'b'
...
>>> b()
b
>>> a.__call__ = b
>>> a()
a
>>> a.__call__ = b.__call__
>>> a()
a

a.__call__ लिए कुछ भी निर्दिष्ट करना व्यर्थ है।

तथापि:

>>> A.__call__ = b.__call__
>>> a()
b

TLDR;

a() a.__call__ कॉल नहीं करता है। यह कॉल type(a).__call__(a)

लिंक

इसका एक अच्छा विवरण है कि "क्यों type(x).__enter__(x) .____ के उत्तर में होता है? X__ के बजाय type(x).__enter__(x) x.__enter__() पायथन मानक संदर्भ में?"

यह व्यवहार विशेष विधि लुकअप पर पायथन दस्तावेज़ीकरण में प्रलेखित है।





python-mock