mock python unit-testing attributes mocking python-unittest

python - pytest mock



parcheando una clase produce "AttributeError: el objeto simulado no tiene ningĂșn atributo" al acceder a los atributos de instancia (1)

No, la exploración automática no puede imitar los atributos establecidos en el método __init__ de la clase original (o en cualquier otro método). Solo puede simular atributos estáticos , todo lo que se puede encontrar en la clase.

De lo contrario, el simulacro tendría que crear una instancia de la clase que intentó reemplazar con un simulacro en primer lugar, lo cual no es una buena idea (piense en las clases que crean una gran cantidad de recursos reales cuando se crea una instancia).

La naturaleza recursiva de una simulación auto-especificada se limita entonces a esos atributos estáticos; si foo es un atributo de clase, al acceder a Foo().foo devolverá una simulación auto-especificada para ese atributo. Si tiene una clase de Spam cuyos atributos son un objeto de tipo Ham , entonces la simulación de Spam.eggs será una simulación de la clase de Ham .

La documentación que lea explícitamente cubre esto:

Un problema más serio es que es común que los atributos de instancia se creen en el método __init__ y no existan en la clase. autospec no puede conocer los atributos creados dinámicamente y restringe la API a los atributos visibles.

Solo debes establecer los atributos que faltan:

@patch(''foo.Foo'', autospec=TestFoo) def test_patched(self, mock_Foo): mock_Foo.return_value.foo = ''foo'' Bar().bar()

o cree una subclase de su clase Foo para propósitos de prueba que agregue el atributo como un atributo de clase:

class TestFoo(foo.Foo): foo = ''foo'' # class attribute @patch(''foo.Foo'', autospec=TestFoo) def test_patched(self, mock_Foo): Bar().bar()

El problema
El uso de mock.patch con autospec=True para parchar una clase no preserva los atributos de las instancias de esa clase.

Los detalles
Estoy intentando probar una Bar clase que crea una instancia de la clase Foo como un atributo de objeto de Bar llamado foo . El método de Bar bajo prueba se llama bar ; llama al método foo de la instancia de Foo perteneciente a Bar . Al probar esto, me estoy burlando de Foo , ya que solo quiero probar que Bar está accediendo al miembro de Foo correcto:

import unittest from mock import patch class Foo(object): def __init__(self): self.foo = ''foo'' class Bar(object): def __init__(self): self.foo = Foo() def bar(self): return self.foo.foo class TestBar(unittest.TestCase): @patch(''foo.Foo'', autospec=True) def test_patched(self, mock_Foo): Bar().bar() def test_unpatched(self): assert Bar().bar() == ''foo''

Las clases y los métodos funcionan bien (pases test_unpatched ), pero cuando trato de Foo en un caso de prueba (probado usando tanto nosestests como pytest) usando autospec=True , encuentro "AttributeError: el objeto autospec=True no tiene el atributo ''foo''"

19:39 $ nosetests -sv foo.py test_patched (foo.TestBar) ... ERROR test_unpatched (foo.TestBar) ... ok ====================================================================== ERROR: test_patched (foo.TestBar) ---------------------------------------------------------------------- Traceback (most recent call last): File "/usr/local/lib/python2.7/dist-packages/mock.py", line 1201, in patched return func(*args, **keywargs) File "/home/vagrant/dev/constellation/test/foo.py", line 19, in test_patched Bar().bar() File "/home/vagrant/dev/constellation/test/foo.py", line 14, in bar return self.foo.foo File "/usr/local/lib/python2.7/dist-packages/mock.py", line 658, in __getattr__ raise AttributeError("Mock object has no attribute %r" % name) AttributeError: Mock object has no attribute ''foo''

De hecho, cuando mock_Foo.return_value.__dict__ , puedo ver que foo no está en la lista de hijos o métodos:

{''_mock_call_args'': None, ''_mock_call_args_list'': [], ''_mock_call_count'': 0, ''_mock_called'': False, ''_mock_children'': {}, ''_mock_delegate'': None, ''_mock_methods'': [''__class__'', ''__delattr__'', ''__dict__'', ''__doc__'', ''__format__'', ''__getattribute__'', ''__hash__'', ''__init__'', ''__module__'', ''__new__'', ''__reduce__'', ''__reduce_ex__'', ''__repr__'', ''__setattr__'', ''__sizeof__'', ''__str__'', ''__subclasshook__'', ''__weakref__''], ''_mock_mock_calls'': [], ''_mock_name'': ''()'', ''_mock_new_name'': ''()'', ''_mock_new_parent'': <MagicMock name=''Foo'' spec=''Foo'' id=''38485392''>, ''_mock_parent'': <MagicMock name=''Foo'' spec=''Foo'' id=''38485392''>, ''_mock_wraps'': None, ''_spec_class'': <class ''foo.Foo''>, ''_spec_set'': None, ''method_calls'': []}

Mi entendimiento de autospec es que, si es Verdadero, las especificaciones del parche deberían aplicarse recursivamente. Dado que foo es de hecho un atributo de las instancias de Foo, ¿no debería ser parcheado? Si no, ¿cómo obtengo el simulacro de Foo para preservar los atributos de las instancias de Foo?

NOTA:
Este es un ejemplo trivial que muestra el problema básico. En realidad, me estoy burlando de un módulo de terceros. Class - consul.Consul - cuyo cliente instancia en la clase de envoltorio de cónsul que tengo. Como no mantengo el módulo de cónsul, no puedo modificar la fuente para que se adapte a mis pruebas (de todas formas no querría hacerlo). Para lo que vale, consul.Consul() devuelve un cliente de cónsul, que tiene un atributo kv , una instancia de consul.Consul.KV . kv tiene un método get , que estoy envolviendo en un método de instancia get_key en mi clase Cónsul. Después de parchear el consul.Consul , la llamada a obtener falla debido a AttributeError: El objeto consul.Consul no tiene ningún atributo kv.

Recursos ya revisados:

http://mock.readthedocs.org/en/latest/helpers.html#autospeccing http://mock.readthedocs.org/en/latest/patch.html