2014-11-06 44 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() 
相關問題