如何修复mock_open调用差异但结果一致的问题
使用 mock_open,我可以通过 with [...] as
这种写法来捕捉写入的数据。不过,验证我得到的数据是否正确有点棘手。例如,我可以这样做:
>>> from mock import mock_open
>>> m = mock_open()
>>> with patch('__main__.open', m, create=True):
... with open('foo', 'w') as h:
... h.write('some stuff')
...
>>> m.mock_calls
[call('foo', 'w'),
call().__enter__(),
call().write('some stuff'),
call().__exit__(None, None, None)]
>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')
但我想对比一下我认为应该写入的内容和实际写入的内容。也就是说,我想做类似这样的事情:
>>> expected = 'some stuff'
>>> assert(expected == m.all_that_was_written)
我遇到的问题是,call
的不同版本的 json(2.0.9 和 1.9)似乎输出的格式不一样。并不是说我可以简单地更新到最新的 json。
我实际遇到的错误是:
E AssertionError: [call('Tool_000.json', 'w'),
call().__enter__(),
call().write('['),
call().write('\n '),
call().write('"1.0.0"'),
call().write(', \n '),
call().write('"2014-02-27 08:58:02"'),
call().write(', \n '),
call().write('"ook"'),
call().write('\n'),
call().write(']'),
call().__exit__(None, None, None)]
!=
[call('Tool_000.json', 'w'),
call().__enter__(),
call().write('[\n "1.0.0"'),
call().write(', \n "2014-02-27 08:58:02"'),
call().write(', \n "ook"'),
call().write('\n'),
call().write(']'),
call().__exit__(None, None, None)]
实际上,调用的方式不同,但最终结果是一样的。
我正在测试的代码相对简单:
with open(get_new_file_name(), 'w') as fp:
json.dump(lst, fp)
所以,创建另一个传递文件指针的方法似乎有点多余。
3 个回答
1
我将要做的是,写一个方法,这个方法会返回所有调用写入方法时的完整字符串。
class FileIOTestCase(unittest.TestCase):
""" For testing code involving file io operations """
def setUp(self):
""" patches the open function with a mock, to be undone after test. """
self.mo = mock_open()
patcher = patch("builtins.open", self.mo)
patcher.start()
self.addCleanup(patcher.stop)
def get_written_string(self):
return ''.join(c[0][0] for c in self.mo.return_value.write.call_args_list)
下面是一个使用这个方法的例子
class TestWriteFile(FileIOTestCase):
def test_write_file__csv(self):
save.write_file("a,b\n1,2", "directory", "C6L")
self.mo.assert_called_once_with(os.path.join("directory", "C6L.csv"), 'w')
self.assertEqual(self.get_written_string(), "a,b\n1,2")
2
mock_open
这个工具还不够完善。目前它在模拟读取文件时表现不错,但在测试写入文件的内容时功能就不够强大了。这个问题很明显地显示了它的不足之处。
我的建议是,如果你要测试写入的内容,就不要使用 mock_open
。下面是一个替代方案:
import six
import mock
import unittest
class GenTest(unittest.TestCase):
def test_open_mock(self):
io = six.BytesIO()
io_mock = mock.MagicMock(wraps=io)
io_mock.__enter__.return_value = io_mock
io_mock.close = mock.Mock() # optional
with mock.patch.object(six.moves.builtins, 'open', create=True, return_value=io_mock):
# test using with
with open('foo', 'w') as h:
expected = 'some stuff'
h.write(expected)
self.assertEquals(expected, io.getvalue())
# test using file handle directly
io.seek(0); io.truncate() # reset io
expected = 'other stuff'
open('bar', 'w').write(expected)
self.assertEquals(expected, io.getvalue())
# test getvalue after close
io.seek(0); io.truncate() # reset io
expected = 'closing stuff'
f = open('baz', 'w')
f.write(expected)
f.close()
self.assertEquals(expected, io.getvalue())
if __name__ == '__main__':
unittest.main()
2
你可以修改 open()
函数,让它返回一个 StringIO
对象,然后你就可以检查里面的内容了。
with mock.patch('module_under_test.open', create=True) as mock_open:
stream = io.StringIO()
# patching to make getvalue() work after close() or __exit__()
stream.close = mock.Mock(return_value=None)
mock_open.return_value = stream
module_under_test.do_something() # this calls open()
contents = stream.getvalue()
assert(contents == expected)
编辑: 添加了对 stream.close
的修改,以避免在调用 stream.getvalue()
时出现异常。