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

Я использую 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". Итак, в конечном итоге я пытаюсь высмеять ввод пользователей. Я пробовал использовать список для возвращаемого значения, но это не работает.

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

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


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

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


person Nick Humrich    schedule 22.07.2014    source источник
comment
response is not 'y' or 'n' or 'yes' or 'no' в не делать то, что вы думаете. См. Как проверить одну переменную на несколько значений? и вам не следует не использовать is для сравнения строковых значений. используйте == для сравнения значений, а не идентификаторов объектов.   -  person Martijn Pieters    schedule 23.07.2014
comment
И здесь будьте осторожны. Похоже, вы пытаетесь использовать is для сравнения строковых литералов. Не делай этого. Тот факт, что он работает (иногда), является лишь деталью реализации в CPython. Кроме того, response is not 'y' or 'n' or 'yes' or 'no', вероятно, не делает того, что вы думаете ...   -  person mgilson    schedule 23.07.2014
comment
Вы можете использовать @patch('foo.bar', side_effect=['ret1', ret2', 'ret3']).   -  person Acumenus    schedule 08.10.2020


Ответы (2)


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

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

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

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

person Martijn Pieters    schedule 22.07.2014
comment
Есть ли способ сделать это со стандартным mock? Есть ли способ использовать патч с MagicMock, как я делаю со стандартным макетом? - person Nick Humrich; 23.07.2014
comment
@Humdinger: Это особенность класса stardard Mock. - person Martijn Pieters; 23.07.2014
comment
@Humdinger: обратите внимание, что mock.patch() по умолчанию использует MagicMock, подкласс Mock. - person Martijn Pieters; 23.07.2014
comment
Назначение списка работает только с python 3. При тестировании с помощью python 2.7 вместо этого мне нужно использовать итератор (m.side_effect = iter(['foo', 'bar', 'baz'])). - person user686249; 12.08.2015
comment
@ user686249: интересно, потому что список является итерируемым. - person Martijn Pieters; 12.08.2015
comment
@ user686249: вообще-то я не могу это воспроизвести. Не в Python 2.7 и не в Python 3. Свойство Mock.side_effect превращает итератор в итератор. Как именно вы установили побочный эффект? Вы ведь создали экземпляр Mock или MagicMock, верно? - person Martijn Pieters; 13.08.2015
comment
@MartijnPieters: Мое задание выглядит как self.MockSession.post.side_effect = testResponses (testResponses - это список), а self.MockSession.post создается через mock.create_autospec(requests.Session().post), поэтому должно быть MagicMock. Проблема может заключаться в том, что я использую пакет python-mock, который поставляется с openSUSE 13.2, который на самом деле не является актуальным (версия 1.0.1-11.1.4). - person user686249; 14.08.2015
comment
@ user686249: Я посмотрю, если будет время. Обратите внимание, что создание спецификации с create_autospec() из связанного метода… довольно бессмысленно. Лучше специфицировать из requests.Response и установить это как возвращаемое значение (или список значений) для объекта MagicMock(), который исправляет метод post. - person Martijn Pieters; 14.08.2015
comment
@ user686249: Я действительно могу воспроизвести это, потому что спецификация метода дает lambda (функцию), а не MagicMock. Функциональный объект не может иметь свойства, поэтому атрибут side_effect должен быть итерируемым. Однако вы не должны описывать этот метод таким образом. Лучше использовать mock.patch.object(requests.Session, 'post'); что приводит к созданию объекта исправления, который правильно автоматически определяет метод, и правильно поддерживает side_effect. - person Martijn Pieters; 14.08.2015
comment
@MartijnPieters: Спасибо за исследование. Просматривая свой код, я заметил, что то, что я делаю, в любом случае слишком сложно. Мой self.MockSession в любом случае является имитируемым объектом Session с автоматической спецификацией, поэтому исправление его методов вручную совершенно излишне, а присвоение списка side_effect работает так, как рекламируется. Извините за шум (хотя, по крайней мере, я кое-что узнал :-)). - person user686249; 14.08.2015
comment
@MartijnPieters Как мне использовать это с mock.patch? например mock_arg.side_effect = [1, 2] не работает. - person Hussain; 07.06.2018
comment
@Hussain: mock.patch() не особенный. Код в вопросе здесь использует mock.patch(). Я не могу помочь вам с этой небольшой информацией; убедитесь, что вы исправили то, что действительно вызывается, а не является объектом метода. - person Martijn Pieters; 07.06.2018
comment
Что произойдет, если он будет вызван больше раз, чем количество элементов в итерируемом объекте? - person JoeMjr2; 23.02.2019
comment
Что, если я хочу, чтобы он возвращал Foo при первом вызове, а затем возвращал Bar каждый раз, когда он вызывается после этого? - person JoeMjr2; 23.02.2019
comment
@ JoeMjr2: Когда итератор исчерпан, поднимается StopIteration. Вы можете использовать любой итератор, поэтому вы можете использовать itertools.chain(['Foo'], itertools.repeat('Bar')) для создания Foo один раз, а затем навсегда для создания Bar. - person Martijn Pieters; 23.02.2019
comment
просто чтобы сократить решение, вы также можете сделать: m = Mock(side_effect=['foo', 'bar', 'baz']) Или, может быть: m = Mock(side_effect=iter(['foo', 'bar', 'baz'])) как рекомендуется @ user686249. - person rojagtap; 24.05.2021

Вы также можете использовать патч для нескольких возвращаемых значений:

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

Помните, что если вы используете более одного патча для метода, то их порядок будет выглядеть следующим образом:

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

как вы можете видеть здесь, он будет отменен. Первый упомянутый патч будет на последней позиции.

person avinash bhat    schedule 29.03.2021
comment
Это не работает. Он просто возвращает весь список ['a', 'b', 'c'] каждый раз, когда вы вызываете исправленную функцию. - person Cerin; 15.05.2021