Python使用两个不同的文件模拟类中的内置“open

to94eoyn  于 2022-12-17  发布在  Python
关注(0)|答案(5)|浏览(144)

当两个文件都使用上下文管理器时,我很难弄清楚如何模拟在一个类中打开两个文件。我知道如何使用mock模块对一个上下文管理的文件进行模拟,如下所示:

@patch('__builtin__.open')
def test_interface_mapping(self, mock_config):
        m = MagicMock(spec=file)
        handle = m.return_value.__enter__.return_value
        handle.__iter__.return_value = ('aa', 'bb')

我的问题是,当一个类在同一个调用中打开两个不同的文件时,如何执行此操作。在我的示例中,类__init__()将文件预加载到两个Map中。该类在其他类中使用。我希望模拟这两个文件的加载以提供测试数据,以便使用IfAddrConfig对象的其他类可以根据我预加载的测试文件内容进行测试。
下面是一个类的例子,我正在努力使用它在__init__()中加载两个文件,我想模拟这两个文件来加载我的测试注入文件内容。getInterfaceMap()是一个经常被调用的函数,所以我不希望它在每次调用时都加载和解析文件,这就是在__init__()中预加载一次Map的原因。

class IfAddrConfig(object):
    def __init__(self):
        # Initialize the static maps once since they require file operations
        # that we do not want to be calling every time getInterfaceMap() is used
        self.settings_map = self.loadSettings()
        self.config_map = self.loadConfig()

    def loadConfig(self):
        config_map = defaultdict(dict)
        with open(os.path.join('some_path.cfg'), 'r') as stream:
            for line in stream:
                # Parse line and build up config_map entries
        return config_map

    def loadSettings(self):
        settings_map = {}
        with open('another_path.cfg', 'r') as stream:
            for line in stream:
                # Parse line and build up settings_map entries
        return settings_map

    def getInterfaceMap(self, interface):
        # Uses both the settings and config maps to finally create a composite map
        # that is returned to called
        interface_map = {}
        for values in self.config_map.values():
            # Accesss self.settings_map and combine/compare entries with
            # self.config_map values to build new composite mappings that
            # depend on supplied interface value
        return interface_map
nszi6y05

nszi6y051#

您必须使用修补的open对象(mock_open)的side_effect属性,并且不要忘记为__exit__方法设置return_value

@patch('__builtin__.open', spec=open)
def test_interface_mapping(self, mock_open):
    handle1 = MagicMock()
    handle1.__enter__.return_value.__iter__.return_value = ('aa', 'bb')
    handle1.__exit__.return_value=False
    handle2 = MagicMock()
    handle2.__enter__.return_value.__iter__.return_value = ('AA', 'BB')
    handle2.__exit__.return_value=False
    mock_open.side_effect = (handle1, handle2)
    with open("ppp") as f:
        self.assertListEqual(["aa","bb"],[x for x in f])
    with open("ppp") as f:
        self.assertListEqual(["AA","BB"],[x for x in f])

**[EDIT]**我发现了一个更优雅的方法在contextlib中使用时模拟内置的“open”函数

所以你可以把测试改写成

@patch('__builtin__.open', new_callable=mock_open, read_data="aa\nbb")
def test_interface_mapping_new(self, mo):
    handlers = (mo.return_value,mock_open(read_data="AA\nBB").return_value,)
    mo.side_effect = handlers
    with open("ppp") as f:
        self.assertEqual("aa\nbb",f.read())
    with open("ppp") as f:
        self.assertEqual("AA\nBB",f.read())

从python3.4开始,你也可以使用readline(),readlines(),而不用模仿其他的东西。

vuktfyat

vuktfyat2#

如果你需要对文件内容进行更多的控制,你可以使用wrapper函数,它根据文件名替换文件的内容,就像原来的open一样。

import unittest.mock as mock

def my_open(filename):
    if filename == 'file.txt':
        content = "text file\ncontent"
    elif filename == 'second.txt':
        content = 'foobar'
    else:
        raise FileNotFoundError(filename)
    file_object = mock.mock_open(read_data=content).return_value
    file_object.__iter__.return_value = content.splitlines(True)
    return file_object

elif链中,为每个现有文件路径设置“文件内容”。
试验:

# standalone
open_patch = mock.patch('__main__.open', new=my_open)
open_patch.start()

file = open('file.txt')
assert file.read() == "text file\ncontent"
file.close()

open_patch.stop()

#with statement
with mock.patch('__main__.open', new=my_open):
    with open('second.txt') as file:
        assert file.read() == 'foobar'

    # as iterable
    with open('file.txt') as file:
        assert ['text file\n', 'content'] == list(file)

# function decorator
@mock.patch('__main__.open', new=my_open)
def test_patched_open():
    with open('second.txt') as file:
        assert file.readline() == 'foobar'

test_patched_open()
yjghlzjz

yjghlzjz3#

你需要创建两个'file'模拟,并模拟open以在open()被调用时按顺序返回它们。side_effect属性允许你这样做:

@patch('__builtin__.open')
def test_interface_mapping(self, mock_open):
    handle1 = MagicMock('file1').__enter__.return_value
    handle1.__iter__.return_value = ('aa', 'bb')
    handle2 = MagicMock('file2').__enter__.return_value
    handle2.__iter__.return_value = ('foo', 'bar')
    mock_open.return_value.side_effect = (handle1, handle2)

被模拟的open()调用在被调用时首先返回handle1,然后返回handle2。然后,任一对象使用模拟来响应被调用的__enter__(),该模拟返回用于__iter__调用的给定元组。

olhwl3o2

olhwl3o24#

您可以在补丁中使用它来代替mock_open ...

def get_mock_open(files: dict[str, str]):
    def open_mock(filename, *args, **kwargs):
        for expected_filename, content in files.items():
            if filename == expected_filename:
                return mock_open(read_data=content).return_value
        raise FileNotFoundError('(mock) Unable to open {filename}')
    return MagicMock(side_effect=open_mock)

使用

传入文件名的字典:内容,就像这样

def test_multiple_opens():
    content1 = 'abc'
    content2 = 'some\nlines\ncontent'
    files = {'file1': content1,
             'file2': content2}

    with patch('builtins.open', get_mock_open(files)) as open_mock:
        with open('file1', 'r') as file:
            result1 = file.read()
        with open('file2', 'r') as file:
            result2 = file.read()

    assert result1 == content1
    assert result2 == content2
    open_mock.assert_called_with('file2', 'r')
v9tzhpje

v9tzhpje5#

这里有点晚了,但是这里有一个夹具,它将让你以一种更清晰和现代的方式来做这件事,这要归功于更新的python版本上的mocker

@fixture()
def mock_files(mocker, request):
    if not hasattr(request, "param"):
        setattr(request, "param", [""])
    if not isinstance(request.param, list):
        request.param = [request.param]

    mock_files = mocker.patch("builtins.open", mock_open(read_data=request.param[0]))

    if len(request.param) > 1:
        mock_files.side_effect = (mock_open(read_data=param).return_value for param in request.param)

    yield mock_files

相关问题