Python的'模拟' - 响应一个参数

时间:2013-08-25 11:49:02

标签: python unit-testing testing mocking

我需要测试一个打开两个文件的方法,并为每个文件写入不同的数据。编写文件的顺序无关紧要。

以下是我如何使用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中是否可能?

2 个答案:

答案 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'