如何模拟 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框...

问题描述:

如何使用测试以下代码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可能会有点奇怪。相反,请使用patchnew_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:

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

如果您想根据传递给这里的参数设置文件对象( 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

基本上,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, {})
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2787  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1710  
  产品配置管理在企业产品研发与生产过程中扮演着至关重要的角色,它能够确保产品在不同阶段的一致性、可追溯性以及高效的变更管理。PLM(产品生命周期管理)系统作为整合产品全生命周期信息的平台,为产品配置管理提供了强大的支持。随着技术的不断发展,到2025年,PLM系统在支持产品配置管理方面将有一系列值得关注的技术实践。基于人...
plm系统主要干什么的   11  
  敏捷迭代周期与 PLM 流程的适配是现代企业在产品开发过程中面临的重要课题。随着市场竞争的加剧和技术的快速发展,企业需要更加高效、灵活的产品开发模式,以满足客户不断变化的需求。敏捷迭代周期强调快速响应变化、持续交付价值,而 PLM 流程则侧重于产品全生命周期的管理和控制。如何将两者有机结合,优化交付节奏,成为提升企业竞...
plm是什么意思   10  
  在企业的数字化转型进程中,PLM(产品生命周期管理)与ERP(企业资源计划)作为两款重要的企业级系统,发挥着关键作用。然而,很多企业人员对它们之间的区别以及协同逻辑并不十分清晰。深入了解这两者的差异与协同方式,有助于企业更好地规划信息化建设,提升整体运营效率。PLM系统概述PLM系统聚焦于产品从概念设计到退役的全生命周...
国产plm软件   12  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用