Python verspottet mehrere Rückgabewerte

168

Ich verwende pythons mock.patch und möchte den Rückgabewert für jeden Aufruf ändern. Hier ist die Einschränkung: Die zu patchende Funktion hat keine Eingaben, daher kann ich den Rückgabewert nicht basierend auf der Eingabe ändern.

Hier ist mein Code als Referenz.

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

Mein Testcode:

@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.promptist nur eine plattformunabhängige (Python 2 und 3) Version von "input". Letztendlich versuche ich also, die Benutzereingaben zu verspotten. Ich habe versucht, eine Liste für den Rückgabewert zu verwenden, aber das funktioniert nicht.

Sie können sehen, dass ich hier nur eine Endlosschleife bekomme, wenn der Rückgabewert etwas Ungültiges ist. Ich brauche also eine Möglichkeit, den Rückgabewert zu ändern, damit mein Test tatsächlich beendet wird.

(Eine andere Möglichkeit, diese Frage zu beantworten, könnte darin bestehen, zu erklären, wie ich Benutzereingaben in einem Komponententest nachahmen kann.)


Kein Problem mit dieser Frage, vor allem, weil ich nicht in der Lage bin, die Eingaben zu variieren.

Einer der Kommentare der Antwort auf diese Frage ist in die gleiche Richtung, es wurde jedoch keine Antwort / kein Kommentar abgegeben.

Nick Humrich
quelle
3
response is not 'y' or 'n' or 'yes' or 'no'in nicht zu tun , was Sie denken , es tut. Siehe Wie teste ich eine Variable gegen mehrere Werte? und Sie sollten nicht verwenden is, um Zeichenfolgenwerte zu vergleichen, verwenden ==, um Werte zu vergleichen , nicht Objektidentitäten.
Martijn Pieters
Seien Sie auch hier vorsichtig. Es scheint, dass Sie versuchen, isString-Literale zu vergleichen. Tu das nicht. Die Tatsache, dass es (manchmal) funktioniert, ist nur ein Implementierungsdetail in CPython. Auch response is not 'y' or 'n' or 'yes' or 'no'tut wahrscheinlich nicht, was Sie denken, dass es ist ...
mgilson

Antworten:

300

Sie können einen zuweisen iterable zu side_effect, und das Mock wird den nächsten Wert in der Folge bei jedem Aufruf zurückkehren wird:

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

Zitieren der Mock()Dokumentation :

Wenn side_effect eine Iterable ist, gibt jeder Aufruf des Mocks den nächsten Wert aus der Iterable zurück.

Als Nebenwirkung der Test response is not 'y' or 'n' or 'yes' or 'no'wird nicht funktionieren; Sie fragen, ob der Ausdruck (response is not 'y')wahr oder 'y'wahr ist (immer der Fall, eine nicht leere Zeichenfolge ist immer wahr) usw. Die verschiedenen Ausdrücke auf beiden Seiten der orOperatoren sind unabhängig . Siehe Wie teste ich eine Variable gegen mehrere Werte?

Sie sollten auch nicht iszum Testen gegen eine Zeichenfolge verwenden. Der CPython-Interpreter kann unter bestimmten Umständen Zeichenfolgenobjekte wiederverwenden. Dies ist jedoch kein Verhalten, auf das Sie zählen sollten.

Verwenden Sie daher:

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

stattdessen; Dabei werden Gleichheitstests ( ==) verwendet, um festzustellen, ob responseauf eine Zeichenfolge mit demselben Inhalt (Wert) verwiesen wird.

Gleiches gilt für response == 'y' or 'yes'; Verwenden Sie response in ('y', 'yes')stattdessen.

Martijn Pieters
quelle
Gibt es eine Möglichkeit, dies mit dem Standard zu tun mock? Gibt es eine Möglichkeit, Patches mit MagicMock zu verwenden, wie ich es mit Standard-Mock mache?
Nick Humrich
@ Humdinger: Dies ist eine Funktion der Stardard- MockKlasse.
Martijn Pieters
17
Das Zuweisen einer Liste scheint nur mit Python 3 zu funktionieren. Testen mit Python 2.7 Ich muss stattdessen einen Iterator verwenden ( m.side_effect = iter(['foo', 'bar', 'baz'])).
user686249
1
@ user686249: Ich kann dies tatsächlich reproduzieren, da das Aussprechen einer Methode eine lambda(eine Funktion) erzeugt, keine MagicMock. Ein Funktionsobjekt nicht hat Eigenschaften, so dass das side_effectAttribut hat eine iterable sein. Sie sollten die Methode jedoch nicht so spezifizieren. Bessere Verwendung mock.patch.object(requests.Session, 'post'); Dies führt zu einem Patcher-Objekt, das die Methode automatisch automatisch spezifiziert undside_effect ordnungsgemäß unterstützt .
Martijn Pieters
3
@ JoeMjr2: Wenn der Iterator erschöpft ist, StopIterationwird ausgelöst . Sie können jeden Iterator verwenden, also können Sie einmal itertools.chain(['Foo'], itertools.repeat('Bar'))produzieren Foound dann für immer produzieren Bar.
Martijn Pieters