【问题标题】:How to unit test with a mocked file object in Python?如何在 Python 中使用模拟文件对象进行单元测试?
【发布时间】:2013-06-13 14:50:13
【问题描述】:

我有一个类,我通过提供像 parser = ParserClass('/path/to/file') 这样的文件名来实例化它,然后我调用 parser.parse() 方法打开并读取文件。
现在我想对内部发生不好的事情进行单元测试:

with open(filename, 'rb') as fp:
    // do something

将引发正确的异常,所以我想像这样模拟__builtin__.open

from mock import MagicMock, patch
from StringIO import StringIO

test_lines = StringIO("""some test lines, emulating a real file content""")
mock_open = MagicMock(return_value=test_lines)
with patch('__builtin__.open', mock_open):
    self.mock.parse()

但这给了我一个AttributeError: StringIO instance has no attribute '__exit__'
我认为 StringIO 的行为与文件对象完全一样,但似乎并非如此。

我怎样才能用给定的内容(test_lines)和模拟对象来测试这个方法?我应该改用什么?

【问题讨论】:

    标签: python unit-testing python-2.7 mocking


    【解决方案1】:

    您可以继承 StringIO 以提供上下文管理器:

    class ContextualStringIO(StringIO):
        def __enter__(self):
            return self
        def __exit__(self, *args):
            self.close() # icecrime does it, so I guess I should, too
            return False # Indicate that we haven't handled the exception, if received
    
    
    test_lines = ContextualStringIO(...)
    

    粗略推测:如果 StringIO 对象是 file 对象的直接替代品除了缺少上下文管理器,我想知道这是否可行:

    class ContextualStringIO(StringIO, file):
        pass
    

    ContextualStringIOStringIO 继承了它可以进行的文件操作,但其他所有内容都从 file 继承。它看起来很优雅,但可能需要大量测试(或者更熟悉 Python 内部的人来解释为什么这不起作用)。

    【讨论】:

    • TypeError: __exit__() takes exactly 1 argument (4 given)
    • @Walkman:谢谢。我已将其更新为采用任意数量的参数,因为我不会处理__exit__ 可能收到的任何异常。适当的实施应该;我对open 提供的上下文管理器不够熟悉,不知道它是否处理任何异常,或者它是否只是关闭文件并允许重新引发任何异常。
    • From the documentation: 如果套件因异常而退出,并且 __exit__() 方法的返回值为 false,则重新引发异常。如果返回值为 true,则抑制异常,并继续执行 with 语句之后的语句。
    • 现在我在 python 文档中阅读了上下文管理器并查看了 StringIO 的实现,我认为您的解决方案是完美而简单的!谢谢!
    【解决方案2】:

    这是StringIO 未实现上下文管理器协议的已知问题。

    common recipe 如下:

    from contextlib import contextmanager
    
    
    @contextmanager
    def StringIO():
        """Add support for 'with' statement to StringIO - http://bugs.python.org/issue1286
        """
        try:
            from cStringIO import StringIO
        except ImportError:
            from StringIO import StringIO
    
        sio = StringIO()
    
        try:
            yield sio
        finally:
            sio.close()
    

    它实现了StringIO 的上下文管理器协议,并允许在with 语句中使用它。


    更新好吧,我刚刚发现了mock_open的存在,它可以直接从字符串中读取,所以这可能是要走的路。

    【讨论】:

    • 谢谢!这可行,但您应该添加 def StringIO(data)sio = StringIO(data) 以便能够使用参数构造它。
    • 再次强调,更新很重要,mock_open 就是为了这个目的,尽管bugs.python.org/issue21258 它应该是理想的解决方案。
    【解决方案3】:

    或者,您可以使用io.StringIO from the Standard Library

    io 模块为流处理提供 Python 接口。在下面 Python 2.x,这是建议作为内置文件的替代品 对象,但在 Python 3.x 中,它是访问文件的默认接口 和流。

    但是(正如注释所说)这只能与 unicode 类型一起使用。

    【讨论】:

      【解决方案4】:

      mock 库中有a provision,专门用于此目的:

      它确实有一个issue 缺少对默认迭代器的支持(即__iter__ 方法,所以你不能立即执行for line in opened_mock_file),但它可以按照here 的描述解决。

      我已经对@icecrime 的回答投了赞成票,但我觉得他的更新部分似乎不够突出。

      【讨论】:

        猜你喜欢
        • 2019-08-22
        • 2013-02-07
        • 2012-06-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多