如何在with语句中模拟open(使用Python Mock框架)?
我该如何使用 unittest.mock
来测试以下代码:
def testme(filepath):
with open(filepath) as f:
return f.read()
11 个回答
在最新版本的mock库中,你可以使用一个非常实用的mock_open助手:
mock_open(mock=None, read_data=None)
这是一个帮助函数,用来创建一个模拟对象,替代打开文件时使用的open函数。它可以直接用于open函数,也可以作为上下文管理器使用。
mock参数是你想要配置的模拟对象。如果你不提供这个参数(默认是None),那么系统会为你创建一个MagicMock对象,这个对象的功能仅限于标准文件句柄上可用的方法或属性。
read_data是一个字符串,用于文件句柄的读取方法返回。默认情况下,这个字符串是空的。
>>> 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')
Python 3
在Python中,你可以对builtins.open
进行修改,使用mock_open
,这个功能是mock
框架的一部分。使用patch
作为上下文管理器时,它会返回一个用来替代被修改对象的对象:
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")
如果你想把patch
当作装饰器使用,直接把mock_open()
的结果作为new=
参数传给patch
可能会有点奇怪。更好的做法是使用patch
的new_callable=
参数,并且要记住,所有patch
不使用的额外参数都会传给new_callable
函数,这在patch
文档中有说明:
patch()
可以接收任意的关键字参数。这些参数会在构造时传给Mock
(或者new_callable)。
@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")
记住,在这种情况下,patch
会把模拟的对象作为参数传给你的测试函数。
Python 2
在Python 2中,你需要修改的是__builtin__.open
而不是builtins.open
,而且mock
并不是unittest
的一部分,你需要单独使用pip install
来安装并导入它:
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")
在mock 0.7.0版本中,处理这个问题的方法发生了变化,这个版本终于支持模拟Python的协议方法(也就是魔法方法),特别是使用MagicMock这个工具:
http://www.voidspace.org.uk/python/mock/magicmock.html
下面是一个模拟打开文件作为上下文管理器的例子(来自mock文档的示例页面):
>>> 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')