如何使用Python中的模拟文件对象进行单元测试?

kis*_*rgy 9 python unit-testing mocking python-2.7

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

with open(filename, 'rb') as fp:
    // do something
Run Code Online (Sandbox Code Playgroud)

将引发正确的异常,所以我想嘲笑__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()
Run Code Online (Sandbox Code Playgroud)

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

如何使用模拟对象的给定内容(test_lines)测试此方法?我应该用什么呢?

che*_*ner 10

您可以子类化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(...)
Run Code Online (Sandbox Code Playgroud)

总的猜测:如果StringIO对象是插入式替代file,除了为缺乏上下文管理的,我不知道这会工作:

class ContextualStringIO(StringIO, file):
    pass
Run Code Online (Sandbox Code Playgroud)

ContextualStringIO继承了它可以从哪些文件操作StringIO,但其他一切都是从中继承的file.它看起来很优雅,但可能需要进行大量测试(或者更熟悉Python内部的人来解释为什么这不起作用).

  • 现在我在python文档中阅读了上下文管理器并查看了StringIO的实现,我认为您的解决方案是完美而简单的!谢谢! (2认同)

ice*_*ime 8

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

一个常见的配方如下:

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()
Run Code Online (Sandbox Code Playgroud)

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


更新嗯,我刚刚发现它的存在mock_open可以直接从字符串读取,所以它可能是要走的路.

  • 再次强调一下,此更新很重要,`mock_open`是用于此目的的,尽管使用http://bugs.python.org/issue21258,它仍应是理想的解决方案。 (2认同)