Burlarse de una clase: ¿Mock () o patch ()?


116

Estoy usando simulacro con Python y me preguntaba cuál de esos dos enfoques es mejor (léase: más pitónico).

Método uno : simplemente cree un objeto simulado y úselo. El código se ve así:

def test_one (self):
    mock = Mock()
    mock.method.return_value = True 
    self.sut.something(mock) # This should called mock.method and checks the result. 
    self.assertTrue(mock.method.called)

Método dos : use un parche para crear una simulación. El código se ve así:

@patch("MyClass")
def test_two (self, mock):
    instance = mock.return_value
    instance.method.return_value = True
    self.sut.something(instance) # This should called mock.method and checks the result. 
    self.assertTrue(instance.method.called)

Ambos métodos hacen lo mismo. No estoy seguro de las diferencias.

¿Alguien podría iluminarme?


10
Como persona que nunca ha probado Mock () o parche, siento que la primera versión es más clara y muestra lo que quiere hacer, aunque no entiendo la diferencia real. No sé si esto es de alguna ayuda o no, pero pensé que podría ser útil para transmitir lo que podría sentir un programador no iniciado.
Michael Brennan

2
@MichaelBrennan: Gracias por tu comentario. De hecho, es útil.
Sardathrion - contra el abuso SE

Respuestas:


151

mock.patches una criatura muy diferente a mock.Mock. patch reemplaza la clase con un objeto simulado y le permite trabajar con la instancia simulada. Eche un vistazo a este fragmento:

>>> class MyClass(object):
...   def __init__(self):
...     print 'Created MyClass@{0}'.format(id(self))
... 
>>> def create_instance():
...   return MyClass()
... 
>>> x = create_instance()
Created MyClass@4299548304
>>> 
>>> @mock.patch('__main__.MyClass')
... def create_instance2(MyClass):
...   MyClass.return_value = 'foo'
...   return create_instance()
... 
>>> i = create_instance2()
>>> i
'foo'
>>> def create_instance():
...   print MyClass
...   return MyClass()
...
>>> create_instance2()
<mock.Mock object at 0x100505d90>
'foo'
>>> create_instance()
<class '__main__.MyClass'>
Created MyClass@4300234128
<__main__.MyClass object at 0x100505d90>

patchreemplaza MyClassde una manera que le permite controlar el uso de la clase en las funciones que llama. Una vez que parchea una clase, las referencias a la clase son reemplazadas por completo por la instancia simulada.

mock.patchse usa generalmente cuando está probando algo que crea una nueva instancia de una clase dentro de la prueba. mock.Mocklos casos son más claros y preferidos. Si su self.sut.somethingmétodo creó una instancia de en MyClasslugar de recibir una instancia como parámetro, entonces mock.patchsería apropiado aquí.


2
@ D.Shawley cómo parcheamos a una clase instanciada dentro de otra clase que necesita estar bajo prueba.
ravi404

4
@ravz: lea "Dónde parchear " . Esta es una de las cosas más difíciles de conseguir que funcione correctamente.
D.Shawley

Mi prueba simulada es similar al método dos . Quiero que la instancia de MyClass genere una excepción. Probé tanto mock.side_effect como mock.return_value.side_effect y no funcionaron. ¿Qué debo hacer?
Hussain

5
@ D.Shawley El enlace está roto, se puede encontrar aquí ahora: "Dónde
parchear

2
Para parchear un objeto de clase, consulte stackoverflow.com/questions/8469680/…
storm_m2138

27

Tengo un video de YouTube sobre esto.

Respuesta corta: utilícela mockcuando esté pasando lo que quiere que se burlen y patchsi no. De los dos, se prefiere la simulación porque significa que está escribiendo código con la inyección de dependencia adecuada.

Ejemplo tonto:

# Use a mock to test this.
my_custom_tweeter(twitter_api, sentence):
    sentence.replace('cks','x')   # We're cool and hip.
    twitter_api.send(sentence)

# Use a patch to mock out twitter_api. You have to patch the Twitter() module/class 
# and have it return a mock. Much uglier, but sometimes necessary.
my_badly_written_tweeter(sentence):
    twitter_api = Twitter(user="XXX", password="YYY")
    sentence.replace('cks','x') 
    twitter_api.send(sentence)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.