为迭代定制 unittest.mock.mock_open

时间:2022-11-19
本文介绍了为迭代定制 unittest.mock.mock_open的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How should I customize unittest.mock.mock_open to handle this code?

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

我的第一次尝试尝试了 read_data.

My first attempt tried read_data.

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

    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 获取数据,直到数据耗尽.这些方法的模拟非常简单.如果您需要更多控制您提供给测试代码的数据,您需要自己自定义此模拟.默认情况下,read_data 是一个空字符串."

This failed, presumably because the code doesn't use read, readline, or readlines. The documentation for unittest.mock.mock_open says, "read_data is a string for the read(), readline(), and readlines() methods of the file handle to return. Calls to those methods will take data from read_data until it is depleted. The mock of these methods is pretty simplistic. If you need more control over the data that you are feeding to the tested code you will need to customize this mock for yourself. read_data is an empty string by default."

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

As the documentation gave no hint on what kind of customization would be required I tried return_value and side_effect. Neither worked.

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

    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)

推荐答案

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

The mock_open() object does indeed not implement iteration.

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

If you are not using the file object as a context manager, you could use:

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

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

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

Now open() returns an iterator, something that can be directly iterated over just like a file object can be, and it'll also work with next(). It can not, however, be used as a context manager.

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

You can combine this with mock_open() then provide a __iter__ and __next__ method on the return value, with the added benefit that mock_open() also adds the prerequisites for use as a context manager:

# 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, ''))

这里的返回值是从 file 对象 (Python 2) 或 MagicMock 对象/3/library/io.html#in-memory-streams" rel="noreferrer">内存中文件对象(Python 3),但只有readwrite__enter__ 方法已被删除.

The return value here is a MagicMock object specced from the file object (Python 2) or the in-memory file objects (Python 3), but only the read, write and __enter__ methods have been stubbed out.

上述在 Python 2 中不起作用,因为 a) Python 2 期望 next 存在,而不是 __next__ 和 b) next 是不被视为 Mock 中的特殊方法(正确地如此),所以即使您在上面的示例中将 __next__ 重命名为 nexttype返回值不会有 next 方法.对于大多数情况,让文件对象产生一个可迭代而不是一个迭代器就足够了:

The above doesn't work in Python 2 because a) Python 2 expects next to exist, not __next__ and b) next is not treated as a special method in Mock (rightly so), so even if you renamed __next__ to next in the above example the type of the return value won't have a next method. For most cases it would be enough to make the file object produced an iterable rather than an iterator with:

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

任何使用 iter(fileobj) 的代码都可以工作(包括 for 循环).

Any code that uses iter(fileobj) will then work (including a for loop).

Python 跟踪器中的未解决问题旨在弥补这一差距.

这篇关于为迭代定制 unittest.mock.mock_open的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持html5模板网!

上一篇:你最喜欢的 Python 模拟库是什么? 下一篇:python mock - 在不妨碍实现的情况下修补方法

相关文章

最新文章