Python mock使用两个不同的文件在一个类中内置'open'

chr*_*gle 14 python unit-testing mocking contextmanager

我无法弄清楚如何在类中使用上下文管理器时模拟两个文件打开.我知道如何使用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')
Run Code Online (Sandbox Code Playgroud)

我的问题是当一个类在同一个调用中打开两个不同的文件时如何执行此操作.在我的例子中,该类__init__()将文件预加载到两个映射中.该类用于其他类.我想模拟这两个文件的加载以提供我的测试数据,以便可以针对我预加载的测试文件内容测试使用IfAddrConfig对象的其他类.

这是我正在努力加入两个文件的类的示例__init__(),我想要模拟这两个文件加载我的测试注入文件内容.getInterfaceMap()是经常调用的函数,因此我不希望每次调用都加载和解析文件,因此__init__()一次预加载映射的原因.

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
Run Code Online (Sandbox Code Playgroud)

Mic*_*ico 13

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

@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])
Run Code Online (Sandbox Code Playgroud)

[编辑] 我发现了一个更优雅的方法来实现它在上下文中使用模拟内置'开放'功能

所以你可以改写测试

@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())
Run Code Online (Sandbox Code Playgroud)

从python 3.4你可以使用readline(),readlines()而不需要嘲笑任何其他东西.


Mar*_*ers 5

您将创建两个“文件”模拟,并模拟openopen()调用顺序返回这些。该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)
Run Code Online (Sandbox Code Playgroud)

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


war*_*ia1 5

如果您需要对文件内容进行更多控制,则可以使用包装函数。它像原始文件一样根据文件名替换文件的内容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
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)