Python simula múltiples valores de retorno

4 minutos de lectura

avatar de usuario
Nick Humrich

Estoy usando pythons mock.patch y me gustaría cambiar el valor de retorno para cada llamada. Aquí está la advertencia: la función que se parchea no tiene entradas, por lo que no puedo cambiar el valor de retorno en función de la entrada.

Aquí está mi código para referencia.

def get_boolean_response():
    response = io.prompt('y/n').lower()
    while response not in ('y', 'n', 'yes', 'no'):
        io.echo('Not a valid input. Try again'])
        response = io.prompt('y/n').lower()

    return response in ('y', 'yes')

Mi código de prueba:

@mock.patch('io')
def test_get_boolean_response(self, mock_io):
    #setup
    mock_io.prompt.return_value = ['x','y']
    result = operations.get_boolean_response()

    #test
    self.assertTrue(result)
    self.assertEqual(mock_io.prompt.call_count, 2)

io.prompt es solo una versión independiente de la plataforma (python 2 y 3) de “input”. Entonces, en última instancia, estoy tratando de simular la entrada de los usuarios. He intentado usar una lista para el valor de retorno, pero eso no parece funcionar.

Puede ver que si el valor de retorno es algo inválido, simplemente obtendré un bucle infinito aquí. Así que necesito una forma de cambiar eventualmente el valor de retorno, para que mi prueba realmente termine.

(otra forma posible de responder a esta pregunta podría ser explicar cómo podría imitar la entrada del usuario en una prueba unitaria)


No es un duplicado de esta pregunta principalmente porque no tengo la capacidad de variar las entradas.

Uno de los comentarios de la Respuesta sobre esta pregunta está en la misma línea, pero no se ha proporcionado ninguna respuesta/comentario.

  • response is not 'y' or 'n' or 'yes' or 'no' en no haciendo lo que crees que hace. Consulte ¿Cómo pruebo una variable con varios valores? y deberías no usar is para comparar valores de cadena, use == comparar valoresno identidades de objetos.

    – Martijn Pieters

    22 de julio de 2014 a las 20:32


  • También tenga cuidado aquí. Parece que estás tratando de usar is para comparar literales de cadena. No hagas eso. El hecho de que funcione (a veces) es solo un detalle de implementación en CPython. También, response is not 'y' or 'n' or 'yes' or 'no' probablemente no está haciendo lo que crees que es…

    – mgilson

    22 de julio de 2014 a las 20:32


  • Puedes usar @patch('foo.bar', side_effect=['ret1', ret2', 'ret3']).

    – Asclepio

    8 oct 2020 a las 18:22


avatar de usuario
Martijn Pieters

Puede asignar un iterable a side_effecty el simulacro devolverá el siguiente valor en la secuencia cada vez que se llame:

>>> from unittest.mock import Mock
>>> m = Mock()
>>> m.side_effect = ['foo', 'bar', 'baz']
>>> m()
'foo'
>>> m()
'bar'
>>> m()
'baz'

citando el Mock() documentación:

Si efecto secundario es un iterable, cada llamada al simulacro devolverá el siguiente valor del iterable.

  • ¿Hay alguna manera de hacer esto con el estándar? mock? ¿Hay alguna manera de usar el parche con MagicMock como lo estoy haciendo con el simulacro estándar?

    – Nick Humrich

    22 de julio de 2014 a las 20:39

  • @Humdinger: Esta es una característica del stardard Mock clase.

    – Martijn Pieters

    22 de julio de 2014 a las 20:39

  • La asignación de una lista parece funcionar solo con Python 3. Probando con python 2.7 Necesito usar un iterador en su lugar (m.side_effect = iter(['foo', 'bar', 'baz'])).

    – usuario686249

    12 de agosto de 2015 a las 12:54

  • @user686249: De hecho, puedo reproducir esto, porque la especificación de un método produce un lambda (una función), no una MagicMock. Un objeto de función no puede tener propiedades, por lo que side_effect atributo posee ser un iterable. Sin embargo, no debería especificar el método de esa manera. mejor uso mock.patch.object(requests.Session, 'post'); que da como resultado un objeto parcheador que se auto-especifica correctamente en el método, y apoya side_effect adecuadamente.

    – Martijn Pieters

    14 de agosto de 2015 a las 10:27


  • @JoeMjr2: cuando se agota el iterador, StopIteration es elevado. Puede usar cualquier iterador, por lo que podría usar itertools.chain(['Foo'], itertools.repeat('Bar')) para producir Foo una vez, luego producir para siempre Bar.

    – Martijn Pieters

    23 de febrero de 2019 a las 15:38

para múltiples valores de retorno, podemos usar efecto secundario durante la inicialización del parche también y pase iterable a él

muestra.py

def hello_world():
    pass

prueba_muestra.py

from unittest.mock import patch
from sample import hello_world

@patch('sample.hello_world', side_effect=[{'a': 1, 'b': 2}, {'a': 4, 'b': 5}])
def test_first_test(self, hello_world_patched):
    assert hello_world() == {'a': 1, 'b': 2}
    assert hello_world() == {'a': 4, 'b': 5}
    assert hello_world_patched.call_count == 2

También puede usar patch para múltiples valores de retorno:

@patch('Function_to_be_patched', return_value=['a', 'b', 'c'])

Recuerde que si está utilizando más de un parche para un método, el orden se verá así:

@patch('a')
@patch('b')
def test(mock_b, mock_a);
    pass

como se puede ver aquí, se revertirá. El primer parche mencionado estará en la última posición.

  • esto no funciona Simplemente devuelve la lista completa. ['a', 'b', 'c'] cada vez que llame a la función parcheada.

    – Cerín

    15 de mayo de 2021 a las 3:44

¿Ha sido útil esta solución?