Simulación de la función de Python basada en argumentos de entrada

6 minutos de lectura

hemos estado usando Imitar para python por un tiempo.

Ahora, tenemos una situación en la que queremos simular una función.

def foo(self, my_param):
    #do something here, assign something to my_result
    return my_result

Normalmente, la forma de burlarse de esto sería (suponiendo que foo sea parte de un objeto)

self.foo = MagicMock(return_value="mocked!")

Incluso, si llamo a foo() un par de veces, puedo usar

self.foo = MagicMock(side_effect=["mocked once", "mocked twice!"])

Ahora, me enfrento a una situación en la que quiero devolver un valor fijo cuando el parámetro de entrada tiene un valor particular. Entonces, si digamos que “my_param” es igual a “algo”, entonces quiero devolver “my_cool_mock”

Esto parece estar disponible en maqueta para python

when(dummy).foo("something").thenReturn("my_cool_mock")

He estado buscando cómo lograr lo mismo con Mock sin éxito.

¿Algunas ideas?

  • Puede ser que esta respuesta ayude – stackoverflow.com/a/7665754/234606

    – naiquevin

    23 de abril de 2013 a las 6:05

  • @naiquevin Esto resuelve perfectamente el problema amigo, ¡gracias!

    – Juan Antonio Gómez Moriano

    23 de abril de 2013 a las 6:18

  • No tenía idea de que pudieras usar Mocktio con Python, ¡+1 para eso!

    – ben

    23 de junio de 2016 a las 16:28

  • Si su proyecto usa pytest, para tal fin, es posible que desee aprovechar monkeypatch. Monkeypatch es más para “reemplazar esta función por el bien de la prueba”, mientras que Mock es lo que usa cuando también desea verificar el mock_calls o hacer aseveraciones sobre cómo se llamaba y así sucesivamente. Hay un lugar para ambos y, a menudo, uso ambos en diferentes momentos en un archivo de prueba determinado.

    – m_flor

    8 oct 2018 a las 22:51


  • Posible duplicado del objeto Python Mock con el método llamado varias veces

    – Melebio

    12 de junio de 2019 a las 12:59

avatar de usuario
Ámbar

Si side_effect_func es una función, entonces lo que devuelva esa función es lo que llama al retorno simulado. los side_effect_func La función se llama con los mismos argumentos que el simulacro. Esto le permite variar el valor de retorno de la llamada dinámicamente, según la entrada:

>>> def side_effect_func(value):
...     return value + 1
...
>>> m = MagicMock(side_effect=side_effect_func)
>>> m(1)
2
>>> m(2)
3
>>> m.mock_calls
[call(1), call(2)]

http://www.voidspace.org.uk/python/mock/mock.html#calling

  • Solo para facilitar la respuesta, ¿podría cambiar el nombre de la función side_effect a otra cosa? (lo sé, lo sé, es bastante simple, pero mejora la legibilidad el hecho de que el nombre de la función y el nombre del parámetro sean diferentes 🙂

    – Juan Antonio Gómez Moriano

    23 de abril de 2013 a las 6:21

  • @JuanAntonioGomezMoriano Podría, pero en este caso solo estoy citando directamente la documentación, por lo que detesto un poco editar la cita si no está específicamente rota.

    – Ámbar

    23 de abril de 2013 a las 14:14

  • y solo para ser pedante todos estos años después, pero side effect es el término exacto: en.wikipedia.org/wiki/Side_effect_(computer_science)

    – lsh

    4 de julio de 2017 a las 2:11

  • @Ish no se están quejando del nombre de CallableMixin.side_effectpero que la función separada definida en el ejemplo tiene el mismo nombre.

    – perro naranja

    8 de mayo de 2018 a las 14:54

avatar de usuario
Juan Antonio Gómez Moriano

Como se indica en el objeto Python Mock con el método llamado varias veces

Una solución es escribir mi propio efecto secundario

def my_side_effect(*args, **kwargs):
    if args[0] == 42:
        return "Called with 42"
    elif args[0] == 43:
        return "Called with 43"
    elif kwargs['foo'] == 7:
        return "Foo is seven"

mockobj.mockmethod.side_effect = my_side_effect

eso hace el truco

  • Esto me dejó más claro que la respuesta seleccionada, así que gracias por responder a su propia pregunta 🙂

    -Luca Bezerra

    10 de agosto de 2018 a las 19:59

  • Muchas gracias. ¡Esto aclaró mucha confusión y fue fácil de entender!

    – Xonshiz

    5 de marzo a las 12:26

El efecto secundario tiene una función (que también puede ser un función lambda), por lo que para casos simples puede usar:

m = MagicMock(side_effect=(lambda x: x+1))

avatar de usuario
Arseni Panfilov

Si usted “quiero devolver un valor fijo cuando el parámetro de entrada tiene un valor particular”tal vez ni siquiera necesites un simulacro y te vendría bien un dict junto con su get método:

foo = {'input1': 'value1', 'input2': 'value2'}.get

foo('input1')  # value1
foo('input2')  # value2

Esto funciona bien cuando la salida de su falsificación es un cartografía de entrada cuando es un función de entrada que sugeriría usar side_effect según Ámbarla respuesta

También puede utilizar una combinación de ambos si desea conservar Mockcapacidades de (assert_called_once, call_count etc):

self.mock.side_effect = {'input1': 'value1', 'input2': 'value2'}.get

avatar de usuario
Manu Artero

He terminado aquí buscando “cómo simular una función basada en argumentos de entrada” y finalmente resolví esto creando una función auxiliar simple:

def mock_responses(responses, default_response=None):
  return lambda input: responses[input] if input in responses else default_response

Ahora:

my_mock.foo.side_effect = mock_responses(
  {
    'x': 42, 
    'y': [1,2,3]
  })
my_mock.goo.side_effect = mock_responses(
  {
    'hello': 'world'
  }, 
  default_response="hi")
...

my_mock.foo('x') # => 42
my_mock.foo('y') # => [1,2,3]
my_mock.foo('unknown') # => None

my_mock.goo('hello') # => 'world'
my_mock.goo('ey') # => 'hi'

¡Espero que esto ayude a alguien!

avatar de usuario
Pilagod

A pesar de que side_effect puede lograr el objetivo, no es tan conveniente configurar side_effect función para cada caso de prueba.

escribo un peso ligero Imitar (que se llama NextMock) para mejorar el simulacro incorporado para abordar este problema, aquí hay un ejemplo simple:

from nextmock import Mock

m = Mock()

m.with_args(1, 2, 3).returns(123)

assert m(1, 2, 3) == 123
assert m(3, 2, 1) != 123

También es compatible con el comparador de argumentos:

from nextmock import Arg, Mock

m = Mock()

m.with_args(1, 2, Arg.Any).returns(123)

assert m(1, 2, 1) == 123
assert m(1, 2, "123") == 123

Espero que este paquete pueda hacer que las pruebas sean más agradables. Siéntase libre de dar cualquier comentario.

avatar de usuario
Hernán

También puedes usar partial de functools si quiere usar una función que toma parámetros pero la función de la que se está burlando no. Por ejemplo, así:

def mock_year(year):
    return datetime.datetime(year, 11, 28, tzinfo=timezone.utc)
@patch('django.utils.timezone.now', side_effect=partial(mock_year, year=2020))

Esto devolverá un invocable que no acepta parámetros (como timezone.now() de Django), pero mi función mock_year sí.

  • Gracias por esta elegante solución. Me gusta agregar que si su función original tiene parámetros adicionales, deben llamarse con palabras clave en su código de producción o este enfoque no funcionará. Obtienes el error: got multiple values for argument.

    – Erik Kalkoken

    6 de marzo de 2020 a las 10:43

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad