如何模拟with语句中使用的open(使用Python中的Mock框架)?

Dar*_*zer 163 python mocking with-statement

如何使用模拟测试以下代码(使用模拟,修补程序装饰器和Michael Foord的Mock框架提供的标记):

def testme(filepath):
    with open(filepath, 'r') as f:
        return f.read()
Run Code Online (Sandbox Code Playgroud)

Mic*_*ico 196

这些答案中有很多噪音; 几乎所有都是正确但过时而不整洁.mock_openmock框架的一部分,使用起来非常简单.patch用作上下文返回用于替换已修补的对象的对象:您可以使用它来使您的测试更简单.

Python 3.x

builtins而不是__builtin__.

from unittest.mock import patch, mock_open
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
    assert open("path/to/open").read() == "data"
    mock_file.assert_called_with("path/to/open")
Run Code Online (Sandbox Code Playgroud)

Python 2.7

mock不是一部分,unittest你应该补丁__builtin__

from mock import patch, mock_open
with patch("__builtin__.open", mock_open(read_data="data")) as mock_file:
    assert open("path/to/open").read() == "data"
    mock_file.assert_called_with("path/to/open")
Run Code Online (Sandbox Code Playgroud)

装饰案例

如果你patch使用mock_open()'s结果作为装饰器,因为new patch's参数可能有点奇怪.

在这种情况下最好使用new_callable patch's参数,并记住每个patch不使用的额外参数将传递给new_callable函数,如patch文档中所述.

patch()接受任意关键字参数.这些将在构造时传递给Mock(或new_callable).

例如,Python 3.x的装饰版本是:

@patch("builtins.open", new_callable=mock_open, read_data="data")
def test_patch(mock_file):
    assert open("path/to/open").read() == "data"
    mock_file.assert_called_with("path/to/open")
Run Code Online (Sandbox Code Playgroud)

请记住,在这种情况下,patch将添加模拟对象作为测试函数的参数.

  • 如何找到正确的名称进行修补?即,您如何为任意函数找到@patch(在本例中为'builtins.open')的第一个参数? (2认同)

fuz*_*man 124

执行此操作的方法在模拟0.7.0中已更改,最终支持模拟python协议方法(魔术方法),特别是使用MagicMock:

http://www.voidspace.org.uk/python/mock/magicmock.html

模拟open作为上下文管理器的示例(来自模拟文档中的示例页面):

>>> open_name = '%s.open' % __name__
>>> with patch(open_name, create=True) as mock_open:
...     mock_open.return_value = MagicMock(spec=file)
...
...     with open('/some/path', 'w') as f:
...         f.write('something')
...
<mock.Mock object at 0x...>
>>> file_handle = mock_open.return_value.__enter__.return_value
>>> file_handle.write.assert_called_with('something')
Run Code Online (Sandbox Code Playgroud)

  • 在Python 3中,'file'未定义(在MagicMock规范中使用),因此我使用的是io.IOBase. (6认同)
  • "后一种方法"展示了如何在不使用MagicMock的情况下进行*(即它只是Mock如何支持魔术方法的一个例子).如果您使用MagicMock(如上所述),则会为您预先配置__enter __和__exit__. (5认同)
  • 你可以指向你的[博客文章](http://www.voidspace.org.uk/python/weblog/arch_d7_2010_10_02.shtml#e1188),你可以在那里详细解释为什么/如何运作 (4认同)
  • 注意:在 Python3 中,内置的 `file` 不见了! (4认同)

Dav*_*vid 68

使用最新版本的mock,您可以使用非常有用的mock_open帮助器:

mock_open(mock = None,read_data = None)

一个帮助函数来创建一个mock来替换open的使用.它适用于直接调用open或用作上下文管理器.

mock参数是要配置的模拟对象.如果为None(默认值),则将为您创建MagicMock,API仅限于标准文件句柄上可用的方法或属性.

read_data是要返回的文件句柄的read方法的字符串.默认情况下,这是一个空字符串.

>>> from mock import mock_open, patch
>>> m = mock_open()
>>> with patch('{}.open'.format(__name__), m, create=True):
...    with open('foo', 'w') as h:
...        h.write('some stuff')

>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')
Run Code Online (Sandbox Code Playgroud)

  • 手动断言`Mock.call_args_list`比调用任何`Mock.assert_xxx`方法更安全.如果你错误拼写任何后者,作为Mock的属性,他们将永远默默地通过. (2认同)

jlb*_*b83 12

要将mock_open用于一个简单的文件read()(此页面上已经提供的原始mock_open片段更适合写入):

my_text = "some text to return when read() is called on the file object"
mocked_open_function = mock.mock_open(read_data=my_text)

with mock.patch("__builtin__.open", mocked_open_function):
    with open("any_string") as f:
        print f.read()
Run Code Online (Sandbox Code Playgroud)

请注意,根据mock_open的文档,这是专门针对的read(),因此不适用于常见的模式for line in f,例如.

使用python 2.6.6/mock 1.0.1

  • 对于 Python 3+,只需将 `__builtin__` 更改为 `builtins`:/sf/ask/633342181/。根据记录,“builtins”是包含所有顶级函数的模块,请参阅文档:https://docs.python.org/3/library/builtins.html(编辑:有关更多详细信息,请参阅下面更新的答案) (2认同)

小智 9

如果您不再需要任何文件,您可以装饰测试方法:

@patch('builtins.open', mock_open(read_data="data"))
def test_testme():
    result = testeme()
    assert result == "data"
Run Code Online (Sandbox Code Playgroud)


the*_*cer 5

最佳答案很有用,但我对其进行了扩展。

如果您想根据传递给这里的参数设置文件对象(fin as f)的值,open()这是一种方法:

def save_arg_return_data(*args, **kwargs):
    mm = MagicMock(spec=file)
    mm.__enter__.return_value = do_something_with_data(*args, **kwargs)
    return mm
m = MagicMock()
m.side_effect = save_arg_return_array_of_data

# if your open() call is in the file mymodule.animals 
# use mymodule.animals as name_of_called_file
open_name = '%s.open' % name_of_called_file

with patch(open_name, m, create=True):
    #do testing here
Run Code Online (Sandbox Code Playgroud)

基本上,open()将返回一个对象并with调用__enter__()该对象。

为了正确模拟,我们必须模拟open()返回一个模拟对象。然后该模拟对象应该模拟__enter__()对它的调用(MagicMock将为我们执行此操作)以返回我们想要的模拟数据/文件对象(因此mm.__enter__.return_value)。以上述方式使用 2 个模拟执行此操作允许我们捕获传递给的参数open()并将它们传递给我们的do_something_with_data方法。

我将整个模拟文件作为字符串传递给open(),我do_something_with_data看起来像这样:

def do_something_with_data(*args, **kwargs):
    return args[0].split("\n")
Run Code Online (Sandbox Code Playgroud)

这会将字符串转换为列表,因此您可以像处理普通文件一样执行以下操作:

for line in file:
    #do action
Run Code Online (Sandbox Code Playgroud)