我如何嘲笑在with语句中使用的开放(在Python中使用Mock框架)?

我如何testing下面的代码与模拟 (使用模拟 ,补丁装饰和迈克尔福德的模拟框架提供的哨兵):

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

实现这个的方式已经改变了0.7.0模拟,最终支持嘲讽python协议方法(魔术方法),特别是使用MagicMock:

http://www.voidspace.org.uk/python/mock/magicmock.html

作为上下文pipe理器(从模拟文档中的示例页面)嘲笑开放的一个例子:

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

这些答案中有很多噪音; 几乎所有的都是正确的,但过时的,不整齐。 mock_openmock框架的一部分,使用非常简单。 用作上下文的patch返回用来replace补丁的对象:您可以使用它来使testing更简单。

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不是unit testing的一部分,你应该补丁__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") 

装饰案例

如果你使用patch作为装饰器使用mock_open()的结果,因为new patch的参数可能mock_open()

在这种情况下,最好使用new_callable patch的参数,并记住patch不使用的每个额外参数都将按照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将添加模拟对象作为您的testingfunction的参数。

有了最新版本的mock,你可以使用真正有用的mock_open helper:

mock_open(mock = None,read_data = None)

一个辅助函数创build一个模拟来replace使用open。 它可以直接调用或用作上下文pipe理器。

模拟参数是要configuration的模拟对象。 如果没有(默认),那么将为你创build一个MagicMock,API限于在标准文件句柄上可用的方法或属性。

read_data是要返回的文件句柄的读取方法的string。 这是一个默认的空string。

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

更新了Daryl的答案,修复了对Mock类的修改。

 @patch('__builtin__.open') def test_testme(self, open_mock): # # setup # context_manager_mock = Mock() open_mock.return_value = context_manager_mock file_mock = Mock() file_mock.read.return_value = sentinel.file_contents enter_mock = Mock() enter_mock.return_value = file_mock exit_mock = Mock() setattr( context_manager_mock, '__enter__', enter_mock ) setattr( context_manager_mock, '__exit__', exit_mock ) # # exercise # result = cbot.testme(sentinel.filepath) # # verify # self.assertEquals(result, sentinel.file_contents) self.assertEquals(open_mock.call_args, ((sentinel.filepath, 'r'), {})) self.assertEquals(context_manager_mock.method_calls, [('__enter__', (), {}), ('__exit__', (None, None, None), {})]) self.assertEquals(file_mock.method_calls, [('read', (), {})]) 

要使用mock_open作为一个简单的文件read() ( 在这个页面上已经给出的原始mock_open片段更适合于写):

 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 / mock 1.0.1

我可能会迟到一点,但是在另一个模块中调用open而不需要创build一个新文件的时候,这对我来说很有效。

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() ,我可以模拟写入文件而不创build文件。

注意:如果您使用的是使用cython的模块,或者您的程序以任何方式依赖于cython,则需要通过在文件顶部包含import __builtin__来导入cython的__builtin__模块 。 如果你正在使用cython,你将无法模拟通用__builtin__

最好的答案是有用的,但我扩大了一点。

如果你想根据传递给open()的参数来设置你的文件对象的值( f as f ),可以这样做:

 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()返回一个模拟对象。 那么这个模拟对象应该嘲笑MagicMock __enter__()调用( MagicMock会为我们做这个)来返回我们想要的模拟数据/文件对象(因此mm.__enter__.return_value )。 这样做2嘲笑上面的方式允许我们捕获传递给open()的参数,并将它们传递给我们的do_something_with_data方法。

我把整个模拟文件作为一个string传递给open() ,我的do_something_with_data看起来像这样:

 def do_something_with_data(*args, **kwargs): return args[0].split("\n") 

这将string转换为列表,因此您可以像使用普通文件一样执行以下操作:

 for line in file: #do action