2009-08-17 47 views
109

如何測試與嘲笑下面的代碼(使用嘲弄,貼片裝飾和哨兵由Michael Foord's Mock framework提供):如何在with語句中使用open(在Python中使用Mock框架)?

def testme(filepath): 
    with open(filepath, 'r') as f: 
     return f.read() 
+0

@Daryl Spitzer:你能否拋棄元問題(「我知道答案......」)這很混亂。 – 2009-08-17 19:34:27

+0

在過去,當我離開時,人們抱怨說我正在回答我自己的問題。我會嘗試將其移至我的答案。 – 2009-08-17 19:38:16

+1

@Daryl:避免回答自己的問題(通常源自「業障嫖娼」的憂慮)的投訴的最佳方式是將問題和/或答案標記爲「社區wiki」。 – 2009-08-17 19:43:59

回答

103

做到這一點的方式,其中終於支持嘲諷蟒蛇模擬0.7.0已經改變協議方法(魔術方法),特別是使用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') 
+0

哇!這看起來比目前在http://www.voidspace.org.uk/python/mock/magicmock.html的context-manager示例簡單得多,它明確地設置'__enter__'和'__exit__'來模擬對象 - 是後者方法過時,還是有用? – 2011-05-24 16:18:40

+5

「後一種方法」展示瞭如何在沒有*的情況下使用MagicMock(即它只是Mock如何支持魔術方法的一個例子)。如果您使用MagicMock(如上所述),則會爲您預先配置__enter__和__exit__。 – fuzzyman 2011-06-06 19:15:34

+4

你可以指向你的[博客文章](http://www.voidspace.org.uk/python/weblog/arch_d7_2010_10_02.shtml#e1188),在那裏你更詳細地解釋爲什麼/如何工作 – Rodrigue 2011-06-23 19:26:30

58

隨着模擬的最新版本,你可以使用真正有用mock_open幫手:

mock_open(模擬=無了read_data =無)

一個輔助函數來創建一個 嘲笑取代使用開放。它適用於直接調用open或 用作上下文管理器。

模擬參數是要配置的模擬對象。如果無(默認爲 ),則將爲您創建MagicMock,並將API 限制爲標準文件句柄上提供的方法或屬性。

read_data是一個字符串,用於讀取文件句柄的方法,返回 。這是一個默認的空字符串。

>>> 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') 
+0

你如何檢查是否有多個'.write'調用? – n611x007 2015-08-17 11:39:45

+1

@naxa一種方法是將每個期望的參數傳遞給'handle.write.assert_any_call()'。如果訂單很重要,您還可以使用'handle.write.call_args_list'來獲取每個呼叫。 – 2015-09-16 13:28:47

+0

'm.return_value.write.assert_called_once_with('some stuff')'是更好的imo。避免註冊電話。 – Anonymous 2016-07-14 14:40:19

8

要爲簡單的文件read()使用mock_open(原mock_open片斷already given on this page是面向更寫):

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/1.0.1模擬

+0

看起來不錯,但我無法讓它與'for_open_file:'類型的代碼一起工作。我試着用實現'__iter__'的iterable StringIO進行實驗,並使用它來代替'my_text',但沒有運氣。 – 2015-01-15 02:26:00

+0

@EvgeniiPuchkaryov這是專門爲'read()'工作的,所以在你的'for line for open_file'情況下不起作用;我已經編輯了這篇文章,以闡明 – jlb83 2015-01-15 18:38:08

+1

@EvgeniiPuchkaryov'在f中的行:'可以通過將'open()'的返回值模擬爲[一個StringIO對象]來實現支持(http://stackoverflow.com/a /696485分之24325868)。 – 2015-09-10 23:45:14

2

我可能會有點晚了比賽,但這個調用另一個模塊open時無需創建一個新的文件爲我工作。

test.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") 

通過修補__builtin__模塊內的open功能我mock_open(),我可以嘲笑,而無需創建一個寫入文件。

注意:如果您使用的是使用cython的模塊,或者您的程序以任何方式依賴於cython,則需要導入cython's __builtin__ module,其中包括文件頂部的import __builtin__。如果您使用的是cython,您將無法模擬通用的__builtin__

+0

這種方法的一個變種對我來說很有效,因爲大多數被測代碼位於其他模塊中,如下所示。我確實需要確保將'import __builtin__'添加到我的測試模塊中。這篇文章有助於澄清爲什麼這種技術的工作原理和它一樣:http://www.ichimonji10.name/blog/6/ – killthrush 2015-11-21 01:59:52

71

這些答案中有很多噪音;幾乎所有的都是正確的,但過時而且不整齊。 mock_openmock框架的一部分,使用非常簡單。用作上下文的patch返回用於替換已修補的對象的對象:您可以使用它來使測試更簡單。

Python 3.x都有

使用builtins而不是__builtin__

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") 

的Python 2.7

mock不是unittest一部分,你應該修補__builtin__

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") 

裝飾情況下

,如果您使用mock_open()的結果作爲使用patch作爲裝飾newpatch的論點可以b有點奇怪。

在這種情況下,最好使用new_callablepatch的說法,並記住每一個額外的參數,作爲在patch documentation描述patch不使用將被傳遞給new_callable功能。

patch()採用任意關鍵字參數。這些將被傳遞給施工中的模擬(或new_callable)。

例如裝飾版本Python 3.x都有是:

@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將增加模擬對象作爲參數的您測試功能。

+0

對不起,請問'with patch(「builtins.open」,mock_open(read_data =「數據「))作爲mock_file:'被轉換成裝飾器語法?我試過了,但我不確定我需要傳遞給@patch(「builtins.open」,...)作爲第二個參數。 – 2017-05-04 18:20:20

+1

@DrunkenMaster更新..謝謝指出。在這種情況下使用裝飾器並不重要。 – 2017-05-04 20:24:24

+0

格拉齊!我的問題有點複雜(我必須將'mock_open'的'return_value'引導到另一個模擬對象中,並聲明第二個模擬的'return_value'),但是它通過將'mock_open'添加爲'new_callable'來工作。 – 2017-05-04 22:25:03

2

最常見的答案很有用,但我稍微擴展了一下。

如果你想基於傳遞給open()的參數設置你的文件對象(在as ff)的值,這裏有一個方法來做到這一點:

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("\n") 

這把字符串轉換爲一個列表,以便您可以像使用普通文件執行以下操作:

for line in file: 
    #do action 
+0

如果正在測試的代碼以不同的方式處理文件,例如通過調用其函數「readline」,則可以使用所需屬性在函數「do_something_with_data」中返回所需的任何模擬對象。 – user3289695 2017-09-06 13:12:32

相關問題