如何用unittest.mock测试下面的代码:

def testme(filepath):
    with open(filepath) as f:
        return f.read()

当前回答

来自github代码片段,用于修补python中的读写功能。

源代码链接在这里

import configparser
import pytest

simpleconfig = """[section]\nkey = value\n\n"""

def test_monkeypatch_open_read(mockopen):
    filename = 'somefile.txt'
    mockopen.write(filename, simpleconfig)
 
    parser = configparser.ConfigParser()
    parser.read(filename)
    assert parser.sections() == ['section']
 
def test_monkeypatch_open_write(mockopen):
    parser = configparser.ConfigParser()
    parser.add_section('section')
    parser.set('section', 'key', 'value')
 
    filename = 'somefile.txt'
    parser.write(open(filename, 'wb'))
    assert mockopen.read(filename) == simpleconfig

其他回答

上面的答案很有用,但我对它进行了一些扩展。

如果你想根据传递给open()的参数来设置文件对象的值(f in as f),这里有一种方法:

def save_arg_return_data(*args, **kwargs):
    mm = MagicMock(spec=file)
    mm.__enter__.return_value = do_something_with_data(*args, **kwargs)
    return mm
m = MagicMock()
m.side_effect = save_arg_return_array_of_data

# if your open() call is in the file mymodule.animals 
# use mymodule.animals as name_of_called_file
open_name = '%s.open' % name_of_called_file

with patch(open_name, m, create=True):
    #do testing here

基本上,open()将返回一个对象,with将在该对象上调用__enter__()。

为了正确地模拟,必须mock open()返回一个模拟对象。然后,该模拟对象应该模拟对其进行的__enter__()调用(MagicMock将为我们完成此操作),以返回我们想要的模拟数据/文件对象(因此是mm.__enter__.return_value)。用上述方法对2个mock执行此操作允许我们捕获传递给open()的参数,并将它们传递给do_something_with_data方法。

我将整个模拟文件作为字符串传递给open(),我的do_something_with_data看起来像这样:

def do_something_with_data(*args, **kwargs):
    return args[0].split("\n")

这将字符串转换为一个列表,这样你就可以像处理普通文件一样执行以下操作:

for line in file:
    #do action

这样做的方式在mock 0.7.0中已经改变,它最终支持模拟python协议方法(magic方法),特别是使用MagicMock:

http://www.voidspace.org.uk/python/mock/magicmock.html

一个作为上下文管理器打开的mock示例(来自mock文档中的示例页面):

>>> open_name = '%s.open' % __name__
>>> with patch(open_name, create=True) as mock_open:
...     mock_open.return_value = MagicMock(spec=file)
...
...     with open('/some/path', 'w') as f:
...         f.write('something')
...
<mock.Mock object at 0x...>
>>> file_handle = mock_open.return_value.__enter__.return_value
>>> file_handle.write.assert_called_with('something')

在我的例子中,我使用的是pytest,好消息是在Python 3中,单元测试库也可以导入和使用,没有问题。

以下是我的方法。首先,我用可重用的pytest fixture创建了一个conftest.py文件:

from functools import cache
from unittest.mock import MagicMock, mock_open

import pytest
from pytest_mock import MockerFixture


class FileMock(MagicMock):

    def __init__(self, mocker: MagicMock = None, **kwargs):
        super().__init__(**kwargs)

        if mocker:
            self.__dict__ = mocker.__dict__
            # configure mock object to replace the use of open(...)
            # note: this is useful in scenarios where data is written out
            _ = mock_open(mock=self)

    @property
    def read_data(self):
        return self.side_effect

    @read_data.setter
    def read_data(self, mock_data: str):
        """set mock data to be returned when `open(...).read()` is called."""
        self.side_effect = mock_open(read_data=mock_data)

    @property
    @cache
    def write_calls(self):
        """a list of calls made to `open().write(...)`"""
        handle = self.return_value
        write: MagicMock = handle.write
        return write.call_args_list

    @property
    def write_lines(self) -> str:
        """a list of written lines (as a string)"""
        return ''.join([c[0][0] for c in self.write_calls])


@pytest.fixture
def mock_file_open(mocker: MockerFixture) -> FileMock:
    return FileMock(mocker.patch('builtins.open'))

在这里,我决定将read_data作为一个属性,以便更python化。它可以用open()需要返回的任何数据在测试函数中赋值。

在我的测试文件中,命名为test_it_works.py,我有以下测试用例来确认预期的功能:

from unittest.mock import call


def test_mock_file_open_and_read(mock_file_open):
    mock_file_open.read_data = 'hello\nworld!'

    with open('/my/file/here', 'r') as in_file:
        assert in_file.readlines() == ['hello\n', 'world!']

    mock_file_open.assert_called_with('/my/file/here', 'r')


def test_mock_file_open_and_write(mock_file_open):
    with open('/out/file/here', 'w') as f:
        f.write('hello\n')
        f.write('world!\n')
        f.write('--> testing 123 :-)')

    mock_file_open.assert_called_with('/out/file/here', 'w')

    assert call('world!\n') in mock_file_open.write_calls

    assert mock_file_open.write_lines == """\
hello
world!
--> testing 123 :-)
""".rstrip()

点击这里查看要点。

在mock的最新版本中,你可以使用真正有用的mock_open helper:

mock_open(模拟= None, read_data =没有) 对象的帮助函数 用Mock代替open。它适用于open call direct或 用作上下文管理器。 mock参数是要配置的模拟对象。若无( 默认),然后将为您创建一个MagicMock,使用API 仅限于标准文件句柄上可用的方法或属性。 Read_data是文件句柄的读取方法的字符串 回报。默认情况下,这是一个空字符串。

>>> from mock import mock_open, patch
>>> m = mock_open()
>>> with patch('{}.open'.format(__name__), m, create=True):
...    with open('foo', 'w') as h:
...        h.write('some stuff')

>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')

来自github代码片段,用于修补python中的读写功能。

源代码链接在这里

import configparser
import pytest

simpleconfig = """[section]\nkey = value\n\n"""

def test_monkeypatch_open_read(mockopen):
    filename = 'somefile.txt'
    mockopen.write(filename, simpleconfig)
 
    parser = configparser.ConfigParser()
    parser.read(filename)
    assert parser.sections() == ['section']
 
def test_monkeypatch_open_write(mockopen):
    parser = configparser.ConfigParser()
    parser.add_section('section')
    parser.set('section', 'key', 'value')
 
    filename = 'somefile.txt'
    parser.write(open(filename, 'wb'))
    assert mockopen.read(filename) == simpleconfig