Python макет нескольких возвращаемых значений

169

Я использую pythons mock.patch и хотел бы изменить значение возврата для каждого вызова. Вот предостережение: исправляемая функция не имеет входных данных, поэтому я не могу изменить возвращаемое значение на основе входных данных.

Вот мой код для справки.

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

Мой тестовый код:

@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это просто независимая от платформы (python 2 и 3) версия «input». В конечном счете, я пытаюсь смоделировать ввод пользователей. Я попытался использовать список для возвращаемого значения, но это не работает.

Вы можете видеть, что если возвращаемое значение является чем-то недопустимым, я просто получу бесконечный цикл здесь. Поэтому мне нужен способ в конечном итоге изменить возвращаемое значение, чтобы мой тест фактически завершился.

(Другой возможный способ ответить на этот вопрос - объяснить, как я могу имитировать пользовательский ввод в модульном тесте)


Не дублируйте этот вопрос главным образом потому, что у меня нет возможности варьировать входные данные.

Один из комментариев Ответа на этот вопрос находится в том же духе, но ответ / комментарий предоставлен не был.

Ник Хамрич
источник
3
response is not 'y' or 'n' or 'yes' or 'no'в не делать то , что вы думаете , что он делает. См. Как проверить одну переменную на несколько значений? и вы не должны использовать isдля сравнения строковых значений, используйте ==для сравнения значений , а не идентификаторов объектов.
Мартин Питерс
Также будьте осторожны здесь. Кажется, вы пытаетесь использовать isдля сравнения строковых литералов. Не делай этого. Тот факт, что это работает (иногда), является лишь деталью реализации в CPython. Кроме того, response is not 'y' or 'n' or 'yes' or 'no'вероятно, не делает то, что вы думаете ...
mgilson

Ответы:

301

Вы можете назначить итерируемое значение side_effect, и mock будет возвращать следующее значение в последовательности каждый раз, когда оно вызывается:

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

Цитирование Mock()документации :

Если side_effect является итеративным, то каждый вызов mock будет возвращать следующее значение из итерируемого.

Кроме того, тест неresponse is not 'y' or 'n' or 'yes' or 'no' будет работать; Вы спрашиваете, является ли выражение истинным или истинным (всегда так, непустая строка всегда истинна) и т. д. Различные выражения по обе стороны от операторов независимы . См. Как проверить одну переменную на несколько значений?(response is not 'y')'y'or

Вы также не должны использовать isдля проверки строки. Интерпретатор CPython может повторно использовать строковые объекты при определенных обстоятельствах , но это не то поведение, на которое вы должны рассчитывать.

В качестве такового используйте:

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

вместо; это будет использовать тесты равенства ( ==), чтобы определить, responseссылается ли строка на то же содержимое (значение).

То же самое относится к response == 'y' or 'yes'; используйте response in ('y', 'yes')вместо.

Мартейн Питерс
источник
Есть ли способ сделать это со стандартом mock? Есть ли способ использовать патч с MagicMock, как я делаю со стандартным макетом?
Ник Хамрих
@Humdinger: Это особенность Mockкласса Stardard .
Мартин Питерс
17
Назначение списка, похоже, работает только с Python 3. Тестирование на python 2.7 Мне нужно использовать итератор ( m.side_effect = iter(['foo', 'bar', 'baz'])).
user686249
1
@ user686249: Я действительно могу воспроизвести это, потому что выделение из метода дает lambda(функцию), а не a MagicMock. Функциональный объект не может иметь свойств, поэтому side_effectатрибут должен быть итеративным. Вы не должны определять этот метод, хотя. Лучшее использование mock.patch.object(requests.Session, 'post'); это приводит к объекту-патчеру, который правильно автоматически специфицирует метод иside_effect правильно поддерживает .
Мартин Питерс
3
@ JoeMjr2: Когда итератор исчерпан, StopIterationвызывается. Вы можете использовать любой итератор, так что вы можете использовать, itertools.chain(['Foo'], itertools.repeat('Bar'))чтобы произвести Fooодин раз, а затем создать всегда Bar.
Мартин Питерс