El Problema

El uso de mock.patch con autospec=True a la revisión de una clase no es la preservación de los atributos de las instancias de esa clase.

Los Detalles

Estoy tratando de poner a prueba una clase Bar que crea una instancia de la clase Foo como un Bar objeto atributo llamado foo. El Bar método de bajo prueba se llama bar; se llama método foo de la Foo instancia perteneciente a Bar. En prueba de esto, me estoy burlando de Foo, ya que solo quiero probar que Bar es el acceso a la correcta Foo miembro:

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 de trabajo de bien (test_unpatched pasa), pero cuando trato de Foo en un caso de prueba (prueba utilizando tanto nosetests y pytest) utilizando autospec=True, me encuentro con «AttributeError: Mock object no tiene ningún 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 imprimo mock_Foo.return_value.__dict__, puedo ver que foo no está en la lista de los niños 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 comprensión de autospec es que, de ser Cierto, el parche specs debe aplicar de forma recursiva. Desde foo es un atributo de Foo instancias, en caso de no ser parcheado? Si no, ¿cómo puedo obtener los Foo burlarse de preservar los atributos de Foo instancias?

NOTA:

Este es un ejemplo trivial que muestra el problema básico. En realidad, me estoy burlando de un tercero module.Class — consul.Consul — cuyo cliente con el que crear una instancia de un Cónsul clase contenedora que tengo. Como yo no mantener el cónsul módulo, no puedo modificar el código fuente para satisfacer mis pruebas (no realmente quiero hacerlo de todos modos). Para lo que vale, consul.Consul() devuelve un cónsul cliente, 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 Cónsul de la clase. Después de la revisión consul.Consul, la llamada a obtener falla debido a AttributeError: Mock object no tiene ningún atributo kv.

Recursos Ya Marcada:

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

  • Que requeriría el simulacro de crear una instancia de la clase. Eso nunca es una buena idea, porque eso requiere que se ejecute el código que estaba tratando de reemplazar con un simulacro en el primer lugar.
InformationsquelleAutor Clandestine | 2015-07-29

1 Comentario

  1. 36

    No, autospeccing no puede burlarse de salida conjunto de atributos en el __init__ método de la clase original (o en cualquier otro método). Sólo puede burlarse de fuera estática atributos, todo lo que se puede encontrar en la clase.

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

    La naturaleza recursiva de una función de specced simulacro es entonces limitada a los atributos estáticos; si foo es un atributo de clase, el acceso a Foo().foo devolverá un auto-specced simulacros para ese atributo. Si usted tiene una clase Spam cuya eggs atributo es un objeto de tipo Ham, a continuación, el simulacro de Spam.eggs será una auto-specced burlarse de la Ham clase.

    La documentación de leer explícitamente cubre este:

    Un problema más serio es que es común que los atributos de instancia creada en el __init__ método y que no existe en la clase. autospec no puede saber acerca de la creación dinámica de atributos y restringe el uso de la api para los atributos visibles.

    Sólo debe conjunto los atributos que faltan a ti mismo:

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

    o crear una subclase de su Foo clase para propósitos de prueba que se agrega 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()
    • Sí, no estoy seguro de cómo lo echaba de menos. Fue bastante explícito. Supongo que eso es lo que voy a tener que hacer. Yo tenía la esperanza de evitar que para que la prueba para verificar el correcto uso de la Cónsul de la clase. Supongo que voy a tener que depender más pesadas manos las pruebas de integración para que.

Dejar respuesta

Please enter your comment!
Please enter your name here