2011-02-18 7 views
15

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

असल में, मैं निम्नलिखित करना चाहते हैं, लेकिन अजगर (निम्नलिखित कोड रूबी, मोचा लाइब्रेरी का उपयोग कर रहा है) में:

def test_stubbing_an_instance_method_on_all_instances_of_a_class 
    Product.any_instance.stubs(:name).returns('stubbed_name') 
    assert_equal 'stubbed_name', SomeClassThatUsesProduct.get_new_product_name 
    end 

ऊपर से नोट करना महत्वपूर्ण बात यह है कि मैं यह नकली करने की जरूरत है कक्षा के स्तर पर, क्योंकि मुझे वास्तव में जिस चीज का परीक्षण हो रहा है, उसके द्वारा बनाई गई एक घटना पर विधियों को नकल करने की आवश्यकता है।

प्रयोग करें प्रकरण:

मैं एक वर्ग QueryMaker जो RemoteAPI का एक उदाहरण पर एक प्रणाली को बुलाती है। मैं कुछ स्थिरता वापस करने के लिए RemoteAPI.get_data_from_remote_server विधि का नकल करना चाहता हूं। कैसे मैं क्या परिवेश में वह चल रहा है के लिए जाँच करने RemoteAPI कोड के भीतर एक विशेष मामला डाल करने के लिए बिना एक परीक्षण के अंदर ऐसा करते हैं

मैं क्या कार्रवाई में करना चाहता था का उदाहरण:।

# a.py 
class A(object): 
    def foo(self): 
     return "A's foo" 

# b.py 
from a import A 

class B(object): 
    def bar(self): 
     x = A() 
     return x.foo() 

# test.py 
from a import A 
from b import B 

def new_foo(self): 
    return "New foo" 

A.foo = new_foo 

y = B() 
if y.bar() == "New foo": 
    print "Success!" 

उत्तर

1

सबसे आसान तरीका है शायद एक वर्ग विधि का उपयोग करने के लिए। आपको वास्तव में एक उदाहरण विधि का उपयोग करना चाहिए, लेकिन यह उनको बनाने का दर्द है, जबकि एक अंतर्निहित फ़ंक्शन है जो कक्षा विधि बनाता है। एक क्लास विधि के साथ, आपके स्टब को पहले तर्क के रूप में कक्षा (उदाहरण के बजाए) का संदर्भ मिलेगा, लेकिन चूंकि यह एक स्टब है, इससे कोई फर्क नहीं पड़ता। तो:

Product.name = classmethod(lambda cls: "stubbed_name") 

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

संपादित करें: आगे की जांच पड़ताल पर, आप छोड़ सकते हैं classmethod():

Product.name = lambda self: "stubbed_name" 

मैं के रूप में बारीकी से संभव के रूप में मूल विधि के व्यवहार को बनाए रखने की कोशिश कर रहा था, लेकिन ऐसा लगता है कि यह वास्तव में आवश्यक नहीं है की तरह (और नहीं करता है ' टी व्यवहार को संरक्षित करें जैसा कि मैंने आशा की थी, किसी भी तरह)।

+0

मैं बिल्कुल का पालन नहीं कर रहा हूँ कि यह कैसे काम करता है। क्या यह मेरे उपयोग के मामले को पूरा करता है? –

+1

यह चाहिए; आपका उपयोग केस आपके उदाहरण के समान ही है। असल में, कक्षा पर विधि की परिभाषा को बदलकर, आप सभी मामलों पर भी अपनी परिभाषा बदल रहे हैं। 'Classmethod' फ़ंक्शन एक रैपर बनाता है जो मूल रूप से फ़ंक्शन को एक विधि में बदल देता है, और 'lambda' फ़ंक्शन को केवल स्थिर स्ट्रिंग को वापस करने के लिए परिभाषित करता है। – kindall

+0

... लेकिन मेरा संपादन देखें। – kindall

1

मुझे रूबी को यह बिल्कुल बताने के लिए पर्याप्त नहीं है कि आप क्या करने की कोशिश कर रहे हैं, लेकिन __getattr__ विधि देखें। यदि आप इसे अपनी कक्षा में परिभाषित करते हैं, तो पाइथन इसे कॉल करेगा जब कोड आपकी कक्षा की किसी भी विशेषता तक पहुंचने का प्रयास करता है जिसे अन्यथा परिभाषित नहीं किया जाता है। चूंकि आप इसे एक विधि बनना चाहते हैं, इसलिए इसे उस फ्लाई पर एक विधि बनाने की आवश्यकता होगी जो यह लौटाती है।

>>> class Product: 
...  def __init__(self, number): 
...   self.number = number 
...  def get_number(self): 
...   print "My number is %d" % self.number 
...  def __getattr__(self, attr_name): 
...   return lambda:"stubbed_"+attr_name 
... 
>>> p = Product(172) 
>>> p.number 
172 
>>> p.name() 
'stubbed_name' 
>>> p.get_number() 
My number is 172 
>>> p.other_method() 
'stubbed_other_method' 

भी ध्यान रखें कि __getattr__ आवश्यकताओं के नहीं अपने वर्ग के किसी भी अन्य अपरिभाषित विशेषताओं का उपयोग, वरना यह असीम पुनरावर्ती हो जाएगा, विशेषता जो मौजूद नहीं है के लिए __getattr__ बुला।

...  def __getattr__(self, attr_name): 
...   return self.x 
>>> p.y 
Traceback (most recent call last): 
#clipped 
RuntimeError: maximum recursion depth exceeded while calling a Python object 

यह कुछ आप केवल अपने परीक्षण कोड, नहीं उत्पादन कोड से क्या करना चाहते है, तो उत्पादन कोड फ़ाइल में अपने सामान्य वर्ग परिभाषा शब्दों में कहें, तो परीक्षण कोड में __getattr__ विधि (अनबाउंड) को परिभाषित , और फिर इसे बाँध वर्ग के लिए आप चाहते हैं:

#production code 
>>> class Product: 
...  def __init__(self, number): 
...   self.number = number 
...  def get_number(self): 
...   print "My number is %d" % self.number 
...   

#test code 
>>> def __getattr__(self, attr): 
...  return lambda:"stubbed_"+attr_name 
... 
>>> p = Product(172) 
>>> p.number 
172 
>>> p.name() 
Traceback (most recent call last): 
    File "<interactive input>", line 1, in <module> 
AttributeError: Product instance has no attribute 'name' 
>>> Product.__getattr__ = __getattr__ 
>>> p.name() 
'stubbed_name' 

मुझे यकीन है कि यह कैसे एक वर्ग कि पहले से ही __getattribute__ उपयोग कर रहा था (के रूप में करने का विरोध किया __getattr__ साथ प्रतिक्रिया होगी, सभी या नहीं, गुण के लिए __getattribute__ कहा जाता है नहीं कर रहा हूँ वे जीवित हैं)।

आप केवल विशिष्ट तरीकों कि पहले से ही मौजूद के लिए ऐसा करना चाहते हैं, तो आप की तरह कुछ कर सकता है:

#production code 
>>> class Product: 
...  def __init__(self, number): 
...   self.number = number 
...  def get_number(self): 
...   return self.number 
...  
>>> p = Product(172) 
>>> p.get_number() 
172 

#test code 
>>> def get_number(self): 
...  return "stub_get_number" 
... 
>>> Product.get_number = get_number 
>>> p.get_number() 
'stub_get_number' 

या यदि आप वास्तव में सुरुचिपूर्ण बनना चाहता था, तुम क्या कर बनाने के लिए एक आवरण समारोह बना सकते हैं आसान कई तरीके:

#test code 
>>> import functools 
>>> def stubber(fn): 
...  return functools.wraps(fn)(lambda self:"stub_"+fn.__name__) 
... 
>>> Product.get_number = stubber(Product.get_number) 
>>> p.get_number() 
'stub_get_number' 
+0

इस अभ्यास का लक्ष्य केवल परीक्षण करने में सक्षम होने के लिए किसी भी उत्पादन कोड को संशोधित करने से बचाना है। मुझे पूरा यकीन है कि आप जो सुझाव दे रहे हैं वह पूरा नहीं करता है। –

+0

यदि आप यही करना चाहते हैं, तो मुझे पूरा यकीन है कि आप इसे अपने परीक्षण कोड में एक अनबाउंड फ़ंक्शन के रूप में परिभाषित कर सकते हैं, फिर इसे अपने उत्पाद कोड से 'उत्पाद .__ getattr__ = fn_name' – user470379

+0

के माध्यम से कक्षा में बाध्य करें। मैं समझता हूं कि ' __getattr__' का उपयोग किया जाता है, और मैं वास्तव में समझ में नहीं आता कि आपको क्यों लगता है कि यह उपयोगी होगा। मुझे उस विधि को प्रतिस्थापित करने की आवश्यकता है जिसे उस वर्ग पर _already_ परिभाषित किया गया है जिस पर मुझे विधि का नकल करने की आवश्यकता है। –

35

तरीकों बाहर उपहास करने के लिए जब परीक्षण बहुत आम है और वहाँ उपकरण अजगर में यह आपकी मदद करने के बहुत सारे हैं की जरूरत। इस तरह के "बंदर पैचिंग" वर्गों के साथ खतरा यह है कि यदि आप पूर्ववत नहीं करते हैं तो बाद में कक्षा आपके सभी परीक्षणों के लिए अन्य सभी उपयोगों के लिए संशोधित की गई है।

मेरी लाइब्रेरी मॉक, जो कि सबसे लोकप्रिय पायथन मॉकिंग लाइब्रेरीज़ में से एक है, में "पैच" नामक एक सहायक शामिल है जो आपको अपने परीक्षणों के दौरान वस्तुओं और कक्षाओं पर सुरक्षित रूप से पैच विधियों या विशेषताओं में मदद करता है।

http://pypi.python.org/pypi/mock

पैच डेकोरेटर एक संदर्भ प्रबंधक के रूप में या एक परीक्षण डेकोरेटर के रूप में इस्तेमाल किया जा सकता:

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

from a import A 
from b import B 

from mock import patch 

def new_foo(self): 
    return "New foo" 

with patch.object(A, 'foo', new_foo): 
    y = B() 
    if y.bar() == "New foo": 
     print "Success!" 

यह आपके लिए स्वचालित रूप से अनपैचिंग को संभालता है। आप नकली कार्य को स्वयं परिभाषित किए बिना दूर हो सकते हैं:

from mock import patch 

with patch.object(A, 'foo') as mock_foo: 
    mock_foo.return_value = "New Foo" 

    y = B() 
    if y.bar() == "New foo": 
     print "Success!" 
+0

अहह - धन्यवाद। मैंने 'मॉक' देखा, लेकिन मुझे कोई स्पष्ट उदाहरण नहीं दिखाई देता था जहां इसे कहीं और आयात करने वाली चीज़ों को पैच करने के लिए उपयोग किया जाता था। +1 –

+0

बस अपने परीक्षणों में सीधे बंदर पैचिंग से सावधान रहें, यह आसानी से चीजों को तोड़ देता है। mock.patch का उपयोग किसी भी नामस्थान में चीज़ों को पैच करने के लिए किया जा सकता है - या तो मौजूदा नामस्थान में ऑब्जेक्ट्स ('patch.object' के साथ) या नाम से निर्दिष्ट किसी अन्य नामस्थान में: 'पैच (' mymodule.myclass ')'। – fuzzyman

+3

जानना महत्वपूर्ण बात यह है कि यदि आप नाम से पैचिंग कर रहे हैं तो आप उस मॉड्यूल में नाम को पैच करते हैं जिसका उपयोग * * किया गया है, मॉड्यूल में नहीं परिभाषित किया गया है। जैसे अगर मैं मॉड्यूल 'foo' में' SomeClass' के उपयोग को पैच करना चाहता था, यहां तक ​​कि 'SomeClass' को मॉड्यूल' बार 'में परिभाषित किया गया है, लेकिन' foo' में आयात किया गया है, तो आप जो पैच करते हैं वह 'foo.SomeClass' और * नहीं है * 'बार। कुछ क्लास '। कक्षा विधि को पैच करने के लिए इससे कोई फर्क नहीं पड़ता क्योंकि वर्ग * ऑब्जेक्ट * दोनों मॉड्यूल में एक ही ऑब्जेक्ट होगा। अन्य चीजों के लिए यह बहुत मायने रखता है। – fuzzyman

1

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

# a.py 
class A(object): 
    def foo(self): 
     return "A's foo" 

# b.py 
from a import A 

class B(object): 
    def bar(self): 
     x = A() 
     return x.foo() 

# test.py 
from a import A 
from b import B 
import mock 

mocked_a_class = mock.Mock() 
mocked_a_instance = mocked_a_class.return_value 
mocked_a_instance.foo.return_value = 'New foo' 

with mock.patch('b.A', mocked_a_class): # Note b.A not a.A 
    y = B() 
    if y.bar() == "New foo": 
     print "Success!" 

docs में संदर्भित, पैरा शुरू में "समझौता वर्ग पर उदाहरणों के तरीकों पर वापसी मान कॉन्फ़िगर करने के लिए ..."

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