2012-03-18 13 views
20

Sto cercando modi/best practice sui metodi di test definiti in una classe base astratta. Una cosa che posso pensare direttamente è eseguire il test su tutte le sottoclassi concrete della classe base, ma a volte sembra eccessivo.Python - Test di una classe base astratta

Considerate questo esempio:

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" 

E 'possibile testare bar senza fare alcun sottoclasse?

risposta

15

Come correttamente messo da lunaryon, non è possibile. Il vero scopo dell'ABC che contiene metodi astratti è che non sono istanziabili come dichiarato.

Tuttavia, è possibile creare una funzione di utilità che introspects un ABC e crea una classe fittizia, non astratta al volo. Questa funzione può essere richiamata direttamente all'interno del metodo/funzione di test e ti evita di dover digitare il codice della piastra di riscaldamento sul file di prova solo per testare alcuni metodi.

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

Freddo. Proverò a giocherellare con questo codice su alcuni dei miei test :). Grazie! – bow

3

No, non lo è. Lo scopo stesso di abc consiste nel creare classi che non possono essere istanziate a meno che tutti gli attributi astratti non vengano sovrascritti con implementazioni concrete. Quindi è necessario derivare dalla classe base astratta e sovrascrivere tutti i metodi e le proprietà astratti.

+0

Hm..se è così, sarebbe opportuno creare una sottoclasse falsa della classe astratta con i metodi e le proprietà astratti definiti, quindi eseguire il test su di esso? – bow

+3

@bow: Sì, è così che faresti questo. – lunaryorn

15

Ecco quello che ho trovato: se si imposta l'attributo __abstractmethods__ di essere un insieme vuoto sarete in grado di creare un'istanza di classe astratta. Questo comportamento è specificato in PEP 3119:

Se la risultante __abstractmethods__ set non è vuoto, la classe è considerata astratta, e tenta di istanziare alzerà TypeError.

Quindi è sufficiente cancellare questo attributo per la durata dei test.

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

Non puoi creare un'istanza di un:

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

Se si ignora __abstractmethods__ è possibile:

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

funziona in entrambi i modi:

>>> 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 

è anche possibile utilizzare unittest.mock (dalla versione 3.3) per ignorare temporaneamente il comportamento ABC.

>>> 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

Nelle versioni più recenti di Python è possibile utilizzare unittest.mock.patch()

class MyAbcClassTest(unittest.TestCase): 

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

Perché abbiamo davvero bisogno di 'multiple'? –

+0

È possibile simulare più proprietà contemporaneamente con 'multiple()' –

+0

Sì, ma non sembra giustificare il suo utilizzo qui poiché solo un attributo sembra corretto. C'è qualcosa di più semplice che può essere usato? –

2

Forse una versione più compatta del concreter proposta da @jsbueno potrebbe essere:

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

La classe risultante ha ancora tutti i metodi astratti originali (che possono essere ora chiamati, anche se non è probabile che siano utili ...) e possono essere derisi come n eeded.

Problemi correlati