2012-03-18 22 views
20

मैं एक सार आधार वर्ग में परिभाषित परीक्षण विधियों पर तरीकों/सर्वोत्तम प्रथाओं की तलाश में हूं। एक बात जो मैं सीधे सोच सकता हूं वह बेस क्लास के सभी ठोस उप-वर्गों पर परीक्षण कर रही है, लेकिन यह कुछ समय में अत्यधिक दिखाई देती है।पायथन - एक सार आधार वर्ग का परीक्षण

इस उदाहरण पर विचार:

import abc 

class Abstract(object): 

    __metaclass__ = abc.ABCMeta 

    @abc.abstractproperty 
    def id(self): 
     return 

    @abc.abstractmethod 
    def foo(self): 
     print "foo" 

    def bar(self): 
     print "bar" 

यह किसी भी उपवर्गीकरण कर बिना bar परीक्षण करने के लिए संभव है?

उत्तर

15

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

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

def concreter(abclass): 
    """ 
    >>> import abc 
    >>> class Abstract(metaclass=abc.ABCMeta): 
    ...  @abc.abstractmethod 
    ...  def bar(self): 
    ...  return None 

    >>> c = concreter(Abstract) 
    >>> c.__name__ 
    'dummy_concrete_Abstract' 
    >>> c().bar() # doctest: +ELLIPSIS 
    (<abc_utils.Abstract object at 0x...>,(), {}) 
    """ 
    if not "__abstractmethods__" in abclass.__dict__: 
     return abclass 
    new_dict = abclass.__dict__.copy() 
    for abstractmethod in abclass.__abstractmethods__: 
     #replace each abc method or property with an identity function: 
     new_dict[abstractmethod] = lambda x, *args, **kw: (x, args, kw) 
    #creates a new class, with the overriden ABCs: 
    return type("dummy_concrete_%s" % abclass.__name__, (abclass,), new_dict) 
+0

कूल। मैं अपने कुछ परीक्षणों पर इस कोड के साथ झुकाव करने की कोशिश करूंगा :)। धन्यवाद! – bow

3

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

+0

एचएम..यह ऐसा है, तो सार तत्वों और गुणों के साथ अमूर्त वर्ग का फर्जी सबक्लास बनाना बुद्धिमान होगा, फिर उस पर परीक्षण करें? – bow

+3

@bow: हाँ, इस तरह आप यह करेंगे। – lunaryorn

15

यहाँ मैं क्या पाया है है। यह व्यवहार PEP 3119 में निर्दिष्ट किया जाता:

हैं जिसके परिणामस्वरूप __abstractmethods__ सेट गैर खाली, वर्ग सार माना जाता है, और यह दृष्टांत के लिए लेखन त्रुटि बढ़ा देंगे प्रयास करता है।

तो आपको परीक्षण की अवधि के लिए इस विशेषता को साफ़ करने की आवश्यकता है। आप __abstractmethods__ ओवरराइड तो

>>> A() 
Traceback (most recent call last): 
TypeError: Can't instantiate abstract class A with abstract methods foo 

आप कर सकते हैं::

>>> import abc 
>>> class A(metaclass = abc.ABCMeta): 
...  @abc.abstractmethod 
...  def foo(self): pass 

आप नहीं कर सकते एक दृष्टांत

>>> class B(object): pass 
>>> B() #doctest: +ELLIPSIS 
<....B object at 0x...> 

>>> B.__abstractmethods__={"foo"} 
>>> B() 
Traceback (most recent call last): 
TypeError: Can't instantiate abstract class B with abstract methods foo 

तुम भीउपयोग कर सकते हैं:

>>> A.__abstractmethods__=set() 
>>> A() #doctest: +ELLIPSIS 
<....A object at 0x...> 

यह दोनों तरीकों से काम करता है(3.3 से) अस्थायी रूप से एबीसी व्यवहार को ओवरराइड करने के लिए।

>>> class A(metaclass = abc.ABCMeta): 
...  @abc.abstractmethod 
...  def foo(self): pass 
>>> from unittest.mock import patch 
>>> p = patch.multiple(A, __abstractmethods__=set()) 
>>> p.start() 
{} 
>>> A() #doctest: +ELLIPSIS 
<....A object at 0x...> 
>>> p.stop() 
>>> A() 
Traceback (most recent call last): 
TypeError: Can't instantiate abstract class A with abstract methods foo 
14

अजगर के नए संस्करण में आप उपयोग कर सकते हैं unittest.mock.patch()

class MyAbcClassTest(unittest.TestCase): 

    @patch.multiple(MyAbcClass, __abstractmethods__=set()) 
    def test(self): 
     self.instance = MyAbcClass() # Ha! 
+0

हमें वास्तव में 'एकाधिक' क्यों चाहिए? –

+0

आप एकाधिक गुणों को 'एकाधिक() ' –

+0

हां के साथ एक साथ मॉक कर सकते हैं, लेकिन ऐसा लगता है कि यहां केवल एक विशेषता दिखाई नहीं दे रही है। क्या कुछ आसान है जिसका उपयोग किया जा सकता है? –

2

शायद concreter @jsbueno द्वारा प्रस्तावित की एक अधिक कॉम्पैक्ट संस्करण हो सकता है:

def concreter(abclass): 
    class concreteCls(abclass): 
     pass 
    concreteCls.__abstractmethods__ = frozenset() 
    return type('DummyConcrete' + abclass.__name__, (concreteCls,), {}) 

जिसके परिणामस्वरूप वर्ग अभी भी सभी मूल अमूर्त विधियां हैं (जिन्हें अब कहा जा सकता है, भले ही यह उपयोगी होने की संभावना न हो ...) और एन के रूप में मजाक किया जा सकता है eeded।