我需要测试一个打开两个文件的方法,并为每个文件写入不同的数据。编写文件的顺序无关紧要。
以下是我如何使用Mock替换open
来测试只需要打开一个文件的方法:
from io import BytesIO
import mock
class MemorisingBytesIO(BytesIO):
"""Like a BytesIO, but it remembers what its value was when it was closed."""
def close(self):
self.final_value = self.getvalue()
super(MemorisingBytesIO, self).close()
open_mock = mock.Mock()
open_mock.return_value = MemorisingBytesIO()
with mock.patch('__builtin__.open', open_mock):
write_to_the_file() # the function under test
open_mock.assert_called_once_with('the/file.name', 'wb')
assert open_mock.return_value.final_value == b'the data'
我无法修改此方法以使用写入两个文件的方法。我已经考虑过使用side_effect
按顺序返回两个MemorisingBytesIO
,并断言每个都包含正确的数据,但是测试会很脆弱:如果方法中调用的顺序发生变化,测试将失败。
所以我真正想做的是让open_mock
在使用一个文件名调用时返回一个MemorisingBytesIO
,在用另一个文件名调用时返回另一个Mock
。我在其他语言的模拟库中看到过这种情况:在没有子类化{{1}}的Python中是否可能?
答案 0 :(得分:1)
以下方法怎么样? (使用class属性保存文件内容):
from io import BytesIO
import mock
class MemorisingBytesIO(BytesIO):
"""Like a BytesIO, but it remembers what its value was when it was closed."""
contents = {}
def __init__(self, filepath, *args, **kwargs):
self.filepath = filepath
super(MemorisingBytesIO, self).__init__()
def close(self):
self.contents[self.filepath] = self.getvalue()
super(MemorisingBytesIO, self).close()
def write_to_the_file():
with open('a/b.txt', 'wb') as f:
f.write('the data')
with open('a/c.txt', 'wb') as f:
f.write('another data')
#MemorisingBytesIO.contents.clear()
open_mock = mock.Mock(side_effect=MemorisingBytesIO)
with mock.patch('__builtin__.open', open_mock):
write_to_the_file() # the function under test
open_mock.assert_called_once_with('a/b.txt', 'wb')
open_mock.assert_called_once_with('a/c.txt', 'wb')
assert MemorisingBytesIO.contents['a/b.txt'] == b'the data'
assert MemorisingBytesIO.contents['a/c.txt'] == b'another data'
答案 1 :(得分:0)
我后来发现了使用mock
做我原本想要的方法。您可以将side_effect
设置为等于函数;当调用mock时,该函数将传递参数。
In [1]: import mock
In [2]: def print_it(a, b):
...: print b
...: print a
...:
In [3]: m = mock.Mock(side_effect=print_it)
In [4]: m('hello', 2)
2
hello
所以这就是你如何编写原始示例来处理两个文件:
fake_file_1 = MemorisingBytesIO()
fake_file_2 = MemorisingBytesIO()
def make_fake_file(filename, mode):
if filename == 'a/b.txt':
return fake_file_1
elif filename == 'a/c.txt':
return fake_file_2
else:
raise IOError('Wrong file name, Einstein')
open_mock = mock.Mock(side_effect=make_fake_file)
with mock.patch('__builtin__.open', open_mock):
write_to_the_file()
assert ('a/b.txt', 'wb') in open_mock.call_args
assert ('a/c.txt', 'wb') in open_mock.call_args
assert fake_file_1.final_value == 'file 1 data'
assert fake_file_2.final_value == 'file 2 data'