【问题标题】:Python's 'mock' - responding to an argumentPython 的“模拟”——响应参数
【发布时间】:2013-08-25 11:49:01
【问题描述】:

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

这是我如何测试一种只需要打开一个文件的方法,使用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依次返回两个MemorisingBytesIOs,并断言它们中的每一个都包含正确的数据,但是测试会很脆弱:如果方法中调用的顺序发生变化,测试将失败。

所以我真正想做的是让open_mock 在用一个文件名调用它时返回一个MemorisingBytesIO,而在用另一个文件名调用它时返回一个不同的MemorisingBytesIO。我在其他语言的 mocking 库中看到了这一点:在 Python 中是否有可能没有子类化 Mock

【问题讨论】:

    标签: python unit-testing testing mocking


    【解决方案1】:

    下面的方法怎么样? (使用类属性保存文件内容):

    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'
    

    【讨论】:

    • 如果您对我最初的问题的答案感兴趣,请在下面查看我的答案:)
    【解决方案2】:

    从那以后,我发现了使用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'
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-23
      • 1970-01-01
      • 2014-10-29
      • 2018-04-21
      • 2014-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多