¿Cómo me burlo directamente de una superclase con una imitación de python?

Estoy utilizando el framework simulado de python para realizar pruebas (http://www.voidspace.org.uk/python/mock/) y quiero simular una superclase y centrarme en probar el comportamiento agregado de las subclases.

(Para los interesados, he extendido pymongo.collection.Collection y solo quiero probar mi comportamiento adicional. No quiero tener que ejecutar mongodb como otro proceso para realizar pruebas).

Para esta discusión, A es la superclase y B es la subclase. Además, defino las llamadas de superclase directas e indirectas como se muestra a continuación:

class A(object): def method(self): ... def another_method(self): ... class B(A): def direct_superclass_call(self): ... A.method(self) def indirect_superclass_call(self): ... super(A, self).another_method() 

Enfoque # 1

Defina una clase simulada para A llamada MockA y use mock.patch para sustituirla por la prueba en tiempo de ejecución. Esto maneja llamadas directas de superclase. Luego manipule B .__ bases__ para manejar llamadas de superclase indirectas. (vea abajo)

El problema que surge es que tengo que escribir MockA y en algunos casos (como en el caso de pymongo.collection.Collection ) esto puede implicar mucho trabajo para desentrañar todas las llamadas internas para simularse.

Enfoque # 2

El enfoque deseado es utilizar de alguna manera una clase simulacro () () para manejar las llamadas en el simulacro justo a tiempo, así como el valor de retorno definido o el efecto de efecto lateral en la prueba. De esta manera, tengo que hacer menos trabajo al evitar la definición de MockA.

El problema que tengo es que no puedo encontrar la forma de alterar B .__ bases__ para que una instancia de mock.Mock () pueda colocarse como una superclase (es necesario que haga algo de enlace directo aquí). Hasta ahora he determinado que super () examina el MRO y luego llama a la primera clase que define el método en cuestión. No puedo averiguar cómo obtener una superclase para manejar el cheque y tener éxito si se encuentra con una clase simulada. __getattr__ no parece ser usado en este caso. Quiero que supuestamente piense que el método está definido en este punto y luego usar la funcionalidad de simulacros () como siempre.

¿Cómo descubre super () qué atributos están definidos dentro de la clase en la secuencia MRO? ¿Y hay una manera para que yo pueda intervenir aquí y de alguna manera lograr que utilice un simulacro.Mock () sobre la marcha?

 import mock class A(object): def __init__(self, value): self.value = value def get_value_direct(self): return self.value def get_value_indirect(self): return self.value class B(A): def __init__(self, value): A.__init__(self, value) def get_value_direct(self): return A.get_value_direct(self) def get_value_indirect(self): return super(B, self).get_value_indirect() # approach 1 - use a defined MockA class MockA(object): def __init__(self, value): pass def get_value_direct(self): return 0 def get_value_indirect(self): return 0 B.__bases__ = (MockA, ) # - mock superclass with mock.patch('__main__.A', MockA): b2 = B(7) print '\nApproach 1' print 'expected result = 0' print 'direct =', b2.get_value_direct() print 'indirect =', b2.get_value_indirect() B.__bases__ = (A, ) # - original superclass # approach 2 - use mock module to mock out superclass # what does XXX need to be below to use mock.Mock()? #B.__bases__ = (XXX, ) with mock.patch('__main__.A') as mymock: b3 = B(7) mymock.get_value_direct.return_value = 0 mymock.get_value_indirect.return_value = 0 print '\nApproach 2' print 'expected result = 0' print 'direct =', b3.get_value_direct() print 'indirect =', b3.get_value_indirect() # FAILS HERE as the old superclass is called #B.__bases__ = (A, ) # - original superclass 

¿hay alguna manera de intercalar aquí y de alguna manera lograr que utilice un simulacro.Mock () sobre la marcha?

Puede haber mejores enfoques, pero siempre puedes escribir tu propio super() e inyectarlo en el módulo que contiene la clase de la que te estás burlando. Haz que devuelva lo que sea necesario según lo que lo llame.

Puede definir super() en el espacio de nombres actual (en cuyo caso la redefinición solo se aplica al módulo actual después de la definición), o puede import __builtin__ y aplicar la redefinición a __builtin__.super , en cuyo caso se aplicará globalmente. en la sesión de Python.

Puede capturar la super función original (si necesita llamarla desde su implementación) usando un argumento predeterminado:

 def super(type, obj=None, super=super): # inside the function, super refers to the built-in 

Jugué con burlarse de super () como sugerido por kindall. Desafortunadamente, después de un gran esfuerzo, se hizo bastante complicado manejar casos de herencia complejos.

Después de algún trabajo, me di cuenta de que super () accede al __dict__ de las clases directamente cuando resuelve atributos a través del MRO (no hace un tipo de llamada getattr). La solución es extender un objeto mock.MagicMock () y envolverlo con una clase para lograr esto. La clase envuelta puede colocarse en la variable __bases__ de una subclase.

El objeto envuelto refleja todos los atributos definidos de la clase objective al __dict__ de la clase envolvente para que las llamadas super () se resuelvan en los atributos correctamente parcheados dentro del MagicMock interno ().

El siguiente código es la solución que he encontrado para trabajar hasta ahora. Tenga en cuenta que realmente implemento esto dentro de un controlador de contexto. Además, se debe tener cuidado de parchear en los espacios de nombres apropiados si se importa desde otros módulos.

Este es un ejemplo simple que ilustra el enfoque:

 from mock import MagicMock import inspect class _WrappedMagicMock(MagicMock): def __init__(self, *args, **kwds): object.__setattr__(self, '_mockclass_wrapper', None) super(_WrappedMagicMock, self).__init__(*args, **kwds) def wrap(self, cls): # get defined attribtues of spec class that need to be preset base_attrs = dir(type('Dummy', (object,), {})) attrs = inspect.getmembers(self._spec_class) new_attrs = [a[0] for a in attrs if a[0] not in base_attrs] # pre set mocks for attributes in the target mock class for name in new_attrs: setattr(cls, name, getattr(self, name)) # eat up any attempts to initialize the target mock class setattr(cls, '__init__', lambda *args, **kwds: None) object.__setattr__(self, '_mockclass_wrapper', cls) def unwrap(self): object.__setattr__(self, '_mockclass_wrapper', None) def __setattr__(self, name, value): super(_WrappedMagicMock, self).__setattr__(name, value) # be sure to reflect to changes wrapper class if activated if self._mockclass_wrapper is not None: setattr(self._mockclass_wrapper, name, value) def _get_child_mock(self, **kwds): # when created children mocks need only be MagicMocks return MagicMock(**kwds) class A(object): x = 1 def __init__(self, value): self.value = value def get_value_direct(self): return self.value def get_value_indirect(self): return self.value class B(A): def __init__(self, value): super(B, self).__init__(value) def f(self): return 2 def get_value_direct(self): return A.get_value_direct(self) def get_value_indirect(self): return super(B, self).get_value_indirect() # nominal behavior b = B(3) assert b.get_value_direct() == 3 assert b.get_value_indirect() == 3 assert bf() == 2 assert bx == 1 # using mock class MockClass = type('MockClassWrapper', (), {}) mock = _WrappedMagicMock(A) mock.wrap(MockClass) # patch the mock in B.__bases__ = (MockClass, ) A = MockClass # set values within the mock mock.x = 0 mock.get_value_direct.return_value = 0 mock.get_value_indirect.return_value = 0 # mocked behavior b = B(7) assert b.get_value_direct() == 0 assert b.get_value_indirect() == 0 assert bf() == 2 assert bx == 0