Python simula múltiples valores de retorno


168

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 está parcheando no tiene entradas, por lo que no puedo cambiar el valor de retorno en función de la entrada.

Aquí está mi código de 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.promptes solo una versión independiente de plataforma (python 2 y 3) de "input". Así que, en última instancia, estoy tratando de burlarme de 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 no válido, obtendré un bucle infinito aquí. Entonces, necesito una forma de cambiar eventualmente el valor de retorno, para que mi prueba realmente finalice.

(Otra posible forma 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 a esta pregunta es similar, pero no se ha proporcionado respuesta / comentario.


3
response is not 'y' or 'n' or 'yes' or 'no'en no hacer lo que crees que hace. Consulte ¿Cómo pruebo una variable con varios valores? y usted debe no utilizar ispara comparar los valores de cadena, el uso ==de comparar los valores , no identidades de objetos.
Martijn Pieters

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

Respuestas:


300

Puede asignar un iterable a side_effect, y 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 la Mock()documentación :

Si side_effect es iterable, cada llamada al simulacro devolverá el siguiente valor del iterable.

Como acotación al margen, la prueba response is not 'y' or 'n' or 'yes' or 'no'será no trabajar; se pregunta si la expresión (response is not 'y')es verdadera o 'y'verdadera (siempre es el caso, una cadena no vacía siempre es verdadera), etc. Las diversas expresiones a cada lado de los oroperadores son independientes . Consulte ¿Cómo pruebo una variable con varios valores?

Tampoco debe usar ispara probar contra una cadena. El intérprete de CPython puede reutilizar objetos de cadena bajo ciertas circunstancias , pero este no es un comportamiento con el que debe contar.

Como tal, use:

response not in ('y', 'n', 'yes', 'no')

en lugar; esto usará pruebas de igualdad ( ==) para determinar si hace responsereferencia a una cadena con el mismo contenido (valor).

Lo mismo se aplica a response == 'y' or 'yes'; utilizar response in ('y', 'yes')en su lugar.


¿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

@ Humdinger: Esta es una característica de la Mockclase stardard .
Martijn Pieters

17
Asignar una lista parece funcionar solo con python 3. Prueba con python 2.7 Necesito usar un iterador en su lugar ( m.side_effect = iter(['foo', 'bar', 'baz'])).
user686249

1
@ user686249: De hecho, puedo reproducir esto, porque la especificación de un método produce una lambda(una función), no una MagicMock. Un objeto de función no puede tener propiedades, por lo que el side_effectatributo tiene que ser un iterable. Sin embargo, no deberías estar especificando el método así. Mejor uso mock.patch.object(requests.Session, 'post'); eso da como resultado un objeto de parche que se autoespecifica correctamente en el método y es compatible de manera side_effectadecuada.
Martijn Pieters

3
@ JoeMjr2: cuando el iterador se agota, StopIterationse eleva. Puede usar cualquier iterador, por lo que podría usar itertools.chain(['Foo'], itertools.repeat('Bar'))para producir Foouna vez y luego producir para siempre Bar.
Martijn Pieters
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.