自定义unittest.mock.mock_open进行迭代

时间:2014-07-16 11:40:27

标签: python unit-testing mocking iteration python-mock

我应该如何自定义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的documentation表示,“read_data是要返回的文件句柄的read(),readline()和readlines()方法的字符串。对这些方法的调用将从read_data获取数据直到它耗尽。这些方法的模拟非常简单。如果你需要更多地控制你正在为测试代码提供的数据,你需要自己定制这个模拟。默认情况下,read_data是一个空字符串。“ / p>

由于文档没有提示需要进行何种自定义,我尝试了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)

3 个答案:

答案 0 :(得分:33)

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()结合使用,然后在返回值上提供__iter____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, ''))

这里的返回值是从MagicMock对象(Python 2)或in-memory file objects(Python 3)中推出的file对象,但只有read,{ {1}}和write方法已被删除。

上述内容在Python 2中不起作用,因为a)Python 2期望__enter__存在,而不是next和b)__next__不被视为Mock中的特殊方法(这是正确的,所以即使你在上面的例子中将next重命名为__next__,返回值的类型也不会有next方法。对于大多数情况,只需使文件对象生成 iterable 而不是迭代器,就足够了:

next

任何使用# Python 2! m = mock.mock_open(read_data=''.join(self.TEST_TEXT)) m.return_value.__iter__ = lambda self: iter(self.readline, '') 的代码都可以使用(包括iter(fileobj)循环)。

open issue in the Python tracker旨在弥补这一差距。

答案 1 :(得分:7)

从Python 3.6开始,e.User.Id.ToString().Equals("####")方法doesn't support iteration返回的模拟文件类对象。这个错误在2014年报告,并且从2017年开始仍然开放。

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

unittest.mock_open

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

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

答案 2 :(得分:1)

我找到了以下解决方案:

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