Python simule plusieurs valeurs de retour

169

J'utilise pythons mock.patch et j'aimerais changer la valeur de retour pour chaque appel. Voici la mise en garde: la fonction à patcher n'a pas d'entrées, donc je ne peux pas changer la valeur de retour en fonction de l'entrée.

Voici mon code pour référence.

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')

Mon code de test:

@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.promptest juste une version indépendante de la plate-forme (python 2 et 3) de "input". Donc, en fin de compte, j'essaie de se moquer de l'entrée des utilisateurs. J'ai essayé d'utiliser une liste pour la valeur de retour, mais cela ne fonctionne pas.

Vous pouvez voir que si la valeur de retour est quelque chose d'invalide, je vais simplement obtenir une boucle infinie ici. J'ai donc besoin d'un moyen de modifier éventuellement la valeur de retour, de sorte que mon test se termine réellement.

(une autre façon possible de répondre à cette question pourrait être d'expliquer comment je pourrais imiter l'entrée d'utilisateur dans un test unitaire)


Pas un dup de cette question principalement car je n'ai pas la possibilité de faire varier les entrées.

L'un des commentaires de la réponse sur cette question va dans le même sens, mais aucune réponse / commentaire n'a été fourni.

Nick Humrich
la source
3
response is not 'y' or 'n' or 'yes' or 'no'en ne faisant pas ce que vous pensez qu'il fait. Consultez Comment tester une variable par rapport à plusieurs valeurs? et vous ne devez pas utiliser ispour comparer des valeurs de chaîne, utiliser ==pour comparer des valeurs , pas des identités d'objets.
Martijn Pieters
Faites également attention ici. Il semble que vous essayez d'utiliser ispour comparer des chaînes littérales. Ne fais pas ça. Le fait que cela fonctionne (parfois) n'est qu'un détail d'implémentation dans CPython. Aussi, response is not 'y' or 'n' or 'yes' or 'no'ne fait probablement pas ce que vous pensez que c'est ...
mgilson

Réponses:

301

Vous pouvez attribuer un itérable à side_effect, et la maquette renverra la valeur suivante dans la séquence à chaque fois qu'elle est appelée:

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

Citant la Mock()documentation :

Si side_effect est un itérable, chaque appel à la simulation renverra la valeur suivante de l'itérable.

En aparté, le test response is not 'y' or 'n' or 'yes' or 'no'sera pas travailler; vous demandez si l'expression (response is not 'y')est vraie ou 'y'vraie (toujours le cas, une chaîne non vide est toujours vraie), etc. Les différentes expressions de chaque côté des oropérateurs sont indépendantes . Consultez Comment tester une variable par rapport à plusieurs valeurs?

Vous devriez également pas utiliser ispour tester contre une chaîne. L'interpréteur CPython peut réutiliser des objets chaîne dans certaines circonstances , mais ce n'est pas un comportement sur lequel vous devriez compter.

En tant que tel, utilisez:

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

au lieu; cela utilisera des tests d' égalité ( ==) pour déterminer si responseréférence une chaîne avec le même contenu (valeur).

La même chose s'applique à response == 'y' or 'yes'; utiliser à la response in ('y', 'yes')place.

Martijn Pieters
la source
Y a-t-il un moyen de faire cela avec la norme mock? Existe-t-il un moyen d'utiliser le patch avec MagicMock comme je le fais avec une maquette standard?
Nick Humrich
@Humdinger: C'est une fonctionnalité de la Mockclasse stardard .
Martijn Pieters
17
L'attribution d'une liste semble fonctionner uniquement avec python 3. Tester avec python 2.7 Je dois utiliser un itérateur à la place ( m.side_effect = iter(['foo', 'bar', 'baz'])).
user686249
1
@ user686249: Je peux en effet reproduire cela, car spécifier à partir d'une méthode produit un lambda(une fonction), pas un MagicMock. Un objet fonction ne peut pas avoir de propriétés, donc l' side_effectattribut doit être un itérable. Vous ne devriez pas spécifier la méthode comme ça. Meilleure utilisation mock.patch.object(requests.Session, 'post'); cela aboutit à un objet patcher qui spécifie automatiquement la méthode et qui prend en charge side_effectcorrectement.
Martijn Pieters
3
@ JoeMjr2: Lorsque l'itérateur est épuisé, StopIterationest déclenché . Vous pouvez utiliser n'importe quel itérateur, donc vous pouvez utiliser itertools.chain(['Foo'], itertools.repeat('Bar'))pour produire Fooune fois, puis pour toujours produire Bar.
Martijn Pieters