Python模拟多个返回值

Nic*_*ich 132 python unit-testing mocking python-mock

我正在使用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')
Run Code Online (Sandbox Code Playgroud)

我的测试代码:

@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)
Run Code Online (Sandbox Code Playgroud)

io.prompt只是一个独立于平台(python 2和3)版本的"输入".所以最终我试图模拟用户输入.我已经尝试使用列表作为返回值,但这并不能解决问题.

你可以看到,如果返回值是无效的,我将在这里得到一个无限循环.所以我需要一种方法来最终改变返回值,以便我的测试实际完成.

(回答这个问题的另一种可能的方法是解释我如何在单元测试中模仿用户输入)


不是这个问题的重复,主要是因为我没有能力改变输入.

答案中关于这个问题的评论之一是相同的,但没有提供答案/评论.

Mar*_*ers 251

你可以指定一个迭代side_effect,并模拟将序列中的每个被调用时,返回的下一个值:

>>> from unittest.mock import Mock
>>> m = Mock()
>>> m.side_effect = ['foo', 'bar', 'baz']
>>> m()
'foo'
>>> m()
'bar'
>>> m()
'baz'
Run Code Online (Sandbox Code Playgroud)

引用Mock()文档:

如果side_effect是一个可迭代的,那么对mock的每次调用都将返回iterable中的下一个值.

顺便说一句,该测试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')
Run Code Online (Sandbox Code Playgroud)

代替; 这将使用相等 tests(==)来确定是否response引用具有相同内容(值)的字符串.

这同样适用于response == 'y' or 'yes'; 使用response in ('y', 'yes')来代替.

  • 分配列表似乎只适用于python 3.使用python 2.7进行测试我需要使用迭代器(`m.side_effect = iter(['foo','bar','baz'])`). (16认同)
  • @JoeMjr2:当迭代器耗尽时,会引发`StopIteration`。您可以使用任何迭代器,因此您可以使用`itertools.chain(['Foo'], itertools.repeat('Bar'))` 生成一次`Foo`,然后永远生成`Bar`。 (4认同)
  • @Humdinger:这是标准`Mock` 类的一个特性。 (2认同)

小智 16

对于多个返回值,我们也可以在补丁初始化期间使用side_effect并将 iterable 传递给它

样本.py

def hello_world():
    pass
Run Code Online (Sandbox Code Playgroud)

测试样本.py

from unittest.mock import patch
from sample import hello_world

@patch('sample.hello_world', side_effect=[{'a': 1, 'b': 2}, {'a': 4, 'b': 5}])
def test_first_test(self, hello_world_patched):
    assert hello_world() == {'a': 1, 'b': 2}
    assert hello_world() == {'a': 4, 'b': 5}
    assert hello_world_patched.call_count == 2
Run Code Online (Sandbox Code Playgroud)