如何模拟 with 语句中使用的 open(使用 Python 中的 Mock 框架)?
- 2025-01-21 09:01:00
- admin 原创
- 110
问题描述:
如何使用测试以下代码unittest.mock
:
def testme(filepath):
with open(filepath) as f:
return f.read()
解决方案 1:
Python 3
修补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
您需要修补__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")
解决方案 2:
在 mock 0.7.0 中,实现此操作的方法已经改变,它最终支持模拟 python 协议方法(魔术方法),特别是使用 MagicMock:
http://www.voidspace.org.uk/python/mock/magicmock.html
作为上下文管理器模拟打开的示例(来自模拟文档中的示例页面):
>>> 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')
解决方案 3:
使用最新版本的 mock,您可以使用真正有用的mock_open帮助程序:
mock_open(mock=None,read_data=None)
一个辅助函数,用于创建模拟来替代 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')
解决方案 4:
要对简单文件使用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()
请注意,根据 mock_open 的文档,这是专门针对的read()
,因此不适用于常见模式for line in f
,例如。
使用 python 2.6.6 / mock 1.0.1
解决方案 5:
如果不需要进一步的任何文件,您可以装饰测试方法:
@patch('builtins.open', mock_open(read_data="data"))
def test_testme():
result = testeme()
assert result == "data"
解决方案 6:
最佳答案很有用,但我对其进行了一些扩展。
如果您想根据传递给这里的参数设置文件对象( f
in )的值,这里有一种方法可以做到: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
基本上,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("
")
这会将字符串转换为列表,以便您可以像处理普通文件一样执行以下操作:
for line in file:
#do action
解决方案 7:
我可能有点迟了,但是当我调用open
另一个模块而不需要创建新文件时,这对我来说很有用。
测试.py
import unittest
from mock import Mock, patch, mock_open
from MyObj import MyObj
class TestObj(unittest.TestCase):
open_ = mock_open()
with patch.object(__builtin__, "open", open_):
ref = MyObj()
ref.save("myfile.txt")
assert open_.call_args_list == [call("myfile.txt", "wb")]
MyObj.py
class MyObj(object):
def save(self, filename):
with open(filename, "wb") as f:
f.write("sample text")
open
通过将模块内的函数修补__builtin__
到我的mock_open()
,我可以模拟写入文件而无需创建文件。
注意:如果您正在使用使用 cython 的模块,或者您的程序以任何方式依赖于 cython,则需要通过在文件顶部包含来导入cython 的__builtin__
模块。如果您使用 cython,import __builtin__
您将无法模拟通用。__builtin__
解决方案 8:
要使用 unittest 修补内置的 open() 函数:
这对于读取 json 配置的补丁有用。
class ObjectUnderTest:
def __init__(self, filename: str):
with open(filename, 'r') as f:
dict_content = json.load(f)
模拟对象是 open() 函数返回的 io.TextIOWrapper 对象
@patch("<src.where.object.is.used>.open",
return_value=io.TextIOWrapper(io.BufferedReader(io.BytesIO(b'{"test_key": "test_value"}'))))
def test_object_function_under_test(self, mocker):
解决方案 9:
我正在pytest
我的情况下使用它,好消息是,在 Python 3 中unittest
也可以导入和使用该库而不会出现问题。
这是我的方法。首先,我创建一个conftest.py
包含可重复使用的 pytest 装置的文件:
from functools import cache
from unittest.mock import MagicMock, mock_open
import pytest
from pytest_mock import MockerFixture
class FileMock(MagicMock):
def __init__(self, mocker: MagicMock = None, **kwargs):
super().__init__(**kwargs)
if mocker:
self.__dict__ = mocker.__dict__
# configure mock object to replace the use of open(...)
# note: this is useful in scenarios where data is written out
_ = mock_open(mock=self)
@property
def read_data(self):
return self.side_effect
@read_data.setter
def read_data(self, mock_data: str):
"""set mock data to be returned when `open(...).read()` is called."""
self.side_effect = mock_open(read_data=mock_data)
@property
@cache
def write_calls(self):
"""a list of calls made to `open().write(...)`"""
handle = self.return_value
write: MagicMock = handle.write
return write.call_args_list
@property
def write_lines(self) -> str:
"""a list of written lines (as a string)"""
return ''.join([c[0][0] for c in self.write_calls])
@pytest.fixture
def mock_file_open(mocker: MockerFixture) -> FileMock:
return FileMock(mocker.patch('builtins.open'))
我决定将 设为read_data
属性,以便更符合 Python 风格。可以在测试函数中为它分配open()
需要返回的任何数据。
在我的测试文件中,命名为test_it_works.py
,我有以下测试用例来确认预期的功能:
from unittest.mock import call
def test_mock_file_open_and_read(mock_file_open):
mock_file_open.read_data = 'hello
world!'
with open('/my/file/here', 'r') as in_file:
assert in_file.readlines() == ['hello
', 'world!']
mock_file_open.assert_called_with('/my/file/here', 'r')
def test_mock_file_open_and_write(mock_file_open):
with open('/out/file/here', 'w') as f:
f.write('hello
')
f.write('world!
')
f.write('--> testing 123 :-)')
mock_file_open.assert_called_with('/out/file/here', 'w')
assert call('world!
') in mock_file_open.write_calls
assert mock_file_open.write_lines == """\nhello
world!
--> testing 123 :-)
""".rstrip()
在此处查看要点。
解决方案 10:
源自 github 代码片段,用于修补 python 中的读写功能。
来源链接在这里
import configparser
import pytest
simpleconfig = """[section]
key = value
"""
def test_monkeypatch_open_read(mockopen):
filename = 'somefile.txt'
mockopen.write(filename, simpleconfig)
parser = configparser.ConfigParser()
parser.read(filename)
assert parser.sections() == ['section']
def test_monkeypatch_open_write(mockopen):
parser = configparser.ConfigParser()
parser.add_section('section')
parser.set('section', 'key', 'value')
filename = 'somefile.txt'
parser.write(open(filename, 'wb'))
assert mockopen.read(filename) == simpleconfig
解决方案 11:
简单的@patch 和断言
如果您想使用@patch。 open() 在处理程序内部被调用并被读取。
@patch("builtins.open", new_callable=mock_open, read_data="data")
def test_lambda_handler(self, mock_open_file):
lambda_handler(event, {})
扫码咨询,免费领取项目管理大礼包!