28

我应该如何自定义 unittest.mock.mock_open 来处理这段代码?

file: impexpdemo.py
def import_register(register_fn):
    with open(register_fn) as f:
        return [line for line in f]

我的第一次尝试尝试了read_data

class TestByteOrderMark1(unittest.TestCase):
    REGISTER_FN = 'test_dummy_path'
    TEST_TEXT = ['test text 1\n', 'test text 2\n']

    def test_byte_order_mark_absent(self):
        m = unittest.mock.mock_open(read_data=self.TEST_TEXT)
        with unittest.mock.patch('builtins.open', m):
            result = impexpdemo.import_register(self.REGISTER_FN)
            self.assertEqual(result, self.TEST_TEXT)

这失败了,大概是因为代码没有使用 read、readline 或 readlines。unittest.mock.mock_open的文档说,“read_data 是文件句柄的 read()、readline() 和 readlines() 方法返回的字符串。对这些方法的调用将从 read_data 获取数据,直到它耗尽“

由于文档没有提示需要什么样的自定义,我尝试return_valueside_effect. 都没有奏效。

class TestByteOrderMark2(unittest.TestCase):
    REGISTER_FN = 'test_dummy_path'
    TEST_TEXT = ['test text 1\n', 'test text 2\n']

    def test_byte_order_mark_absent(self):
        m = unittest.mock.mock_open()
        m().side_effect = self.TEST_TEXT
        with unittest.mock.patch('builtins.open', m):
            result = impexpdemo.import_register(self.REGISTER_FN)
            self.assertEqual(result, self.TEST_TEXT)
4

3 回答 3

37

mock_open()对象确实没有实现迭代。

如果您不使用文件对象作为上下文管理器,您可以使用:

m = unittest.mock.MagicMock(name='open', spec=open)
m.return_value = iter(self.TEST_TEXT)

with unittest.mock.patch('builtins.open', m):

现在open()返回一个迭代器,它可以像文件对象一样直接迭代,它也可以与next(). 但是,它不能用作上下文管理器。

您可以将其与mock_open()then 在返回值上提供一个__iter__and__next__方法结合使用,其额外的好处是mock_open()还添加了用作上下文管理器的先决条件:

# Note: read_data must be a string!
m = unittest.mock.mock_open(read_data=''.join(self.TEST_TEXT))
m.return_value.__iter__ = lambda self: self
m.return_value.__next__ = lambda self: next(iter(self.readline, ''))

这里的返回值是MagicMockfile对象 (Python 2) 或内存文件对象(Python 3) 指定的对象,但只有read,write__enter__方法已被删除。

以上在 Python 2 中不起作用,因为 a) Python 2 期望next存在,而不是__next__b)next在 Mock 中不被视为特殊方法(正确如此),所以即使您在上面示例中重命名__next__为返回值不会有方法。在大多数情况下,使文件对象生成可迭代而不是迭代器就足够了:nextnext

# Python 2!
m = mock.mock_open(read_data=''.join(self.TEST_TEXT))
m.return_value.__iter__ = lambda self: iter(self.readline, '')

然后使用的任何代码iter(fileobj)都将起作用(包括for循环)。

Python 跟踪器中有一个未解决的问题,旨在弥补这一差距。

于 2014-07-16T11:42:09.150 回答
11

从 Python 3.6 开始,该unittest.mock_open方法返回的模拟文件类对象不支持迭代。此错误于 2014 年报告,截至 2017 年仍处于开放状态。

因此,像这样的代码默默地产生零迭代:

f_open = unittest.mock.mock_open(read_data='foo\nbar\n')
f = f_open('blah')
for line in f:
  print(line)

您可以通过向模拟对象添加一个返回正确行迭代器的方法来解决此限制:

def mock_open(*args, **kargs):
  f_open = unittest.mock.mock_open(*args, **kargs)
  f_open.return_value.__iter__ = lambda self : iter(self.readline, '')
  return f_open
于 2017-01-14T23:37:22.647 回答
2

我找到了以下解决方案:

text_file_data = '\n'.join(["a line here", "the second line", "another 
line in the file"])
with patch('__builtin__.open', mock_open(read_data=text_file_data), 
create=True) as m:
    # mock_open doesn't properly handle iterating over the open file with for line in file:
    # but if we set the return value like this, it works.
    m.return_value.__iter__.return_value = text_file_data.splitlines()
    with open('filename', 'rU') as f:
        for line in f:
            print line
于 2018-05-02T06:55:17.237 回答