una interfaces hacer como clases clase abstractas abstracta python oop testing abc

interfaces - Python-Prueba de una clase base abstracta



como hacer una clase abstracta en python (6)

Estoy buscando formas / mejores prácticas en los métodos de prueba definidos en una clase base abstracta. Una cosa en la que puedo pensar directamente es realizar la prueba en todas las subclases concretas de la clase base, pero eso parece excesivo en algunos momentos.

Considera este ejemplo:

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"

¿Es posible probar la bar sin hacer subclases?


Como lo puso adecuadamente el lunaryon, no es posible. El propósito mismo de los ABC que contienen métodos abstractos es que no son instanciables como se declaran.

Sin embargo, es posible crear una función de utilidad que inspecciona un ABC y crea una clase ficticia, no abstracta sobre la marcha. Esta función podría llamarse directamente dentro de su método / función de prueba y evitarle tener que escribir el código de la placa de caldera en el archivo de prueba solo para probar algunos métodos.

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)


En las versiones más recientes de Python puede usar unittest.mock.patch()

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


Esto es lo que he encontrado: si establece que el atributo __abstractmethods__ sea ​​un conjunto vacío, podrá instanciar una clase abstracta. Este comportamiento se especifica en PEP 3119 :

Si el conjunto __abstractmethods__ resultante no está vacío, la clase se considera abstracta e intenta crear una instancia de TypeError.

Así que solo necesitas borrar este atributo durante la duración de las pruebas.

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

No puede crear una instancia A:

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

Si anula __abstractmethods__ puede:

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

Funciona en ambos sentidos:

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

También puede usar unittest.mock (desde 3.3) para anular temporalmente el comportamiento ABC.

class MyAbcClassTest(unittest.TestCase): @patch.multiple(MyAbcClass, __abstractmethods__=set()) def test(self): self.instance = MyAbcClass() # Ha!


No, no es. El propósito mismo de abc es crear clases que no pueden ser instanciadas a menos que todos los atributos abstractos sean anulados con implementaciones concretas. Por lo tanto, debe derivar de la clase base abstracta y anular todos los métodos y propiedades abstractos.


Puede usar la práctica de herencia múltiple para tener acceso a los métodos implementados de la clase abstracta. Obviamente, seguir esa decisión de diseño depende de la estructura de la clase abstracta, ya que necesita implementar métodos abstractos (al menos traer la firma) en su caso de prueba.

Aquí está el ejemplo para su caso:

class Abstract(object): __metaclass__ = abc.ABCMeta @abc.abstractproperty def id(self): return @abc.abstractmethod def foo(self): print("foo") def bar(self): print("bar") class AbstractTest(unittest.TestCase, Abstract): def foo(self): pass def test_bar(self): self.bar() self.assertTrue(1==1)


Quizás una versión más compacta del concreto que propone @jsbueno podría ser:

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

La clase resultante aún tiene todos los métodos abstractos originales (que ahora se pueden llamar, incluso si es probable que esto no sea útil ...) y se pueden burlar según sea necesario.