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