2014-11-06 12 views
2

我无法弄清楚当他们都使用上下文管理器时,如何模拟两个文件在类中打开。我知道如何使用模拟模块,这样做的一个上下文管理文件:Python模拟内建'打开'在一个类中使用两个不同的文件

@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__()预加载文件到两个地图。这个类用于其他类。我想嘲笑这两个文件的加载,以提供我的测试数据,以便使用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 

回答

4

您必须使用补丁open对象(mock_open)的side_effect属性,不要忘记设置return_value__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]) 

[编辑] 我发现一个更优雅的方式来做到这一点Mock builtin 'open" function when used in contextlib

所以你可以重写测试样

@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()) 

而且从蟒蛇3.4就可以也可以使用readline(),readlines()而不嘲笑其他任何东西。

+0

谢谢!这个答案是正确的,并有帮助。 – chromeeagle 2014-11-10 15:01:41

+0

@Mark如果有用,请不要忘记+1 :) – 2014-11-10 15:23:47

+0

谢谢!我的问题是不同的 - 我怎么嘲笑'打开(文件名)为f:为f中的行:...' - 你的答案帮助我这样做。不幸的是'mock_open'对迭代没有任何帮助。 – 2014-12-04 09:53:08

2

你会创建两个“文件”嘲笑,和模拟openopen()就是所谓的顺序返回这些。该side_effect attribute可以让你做到这一点:

@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__调用返回给定的元组。

+0

你为什么打补丁'__builtin __ open',只能扔掉产生的模仿对象(通过重新定义立即在函数的第一行'mock_open')。? – 2017-10-23 06:35:37

+0

@ron.rothmanℝℝ:很好的问题,确实没有必要这样做。我不记得我为什么这么做了(已经差不多3年了),所以我刚刚删除了第二个模拟。 – 2017-10-23 06:48:56

1

如果您需要对文件内容进行更多的控制,可以使用包装函数。 它根据文件名将文件内容替换为原来的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() 
相关问题