【问题标题】:Python 3 Context Manager Mock Unit TestPython 3 上下文管理器模拟单元测试
【发布时间】:2018-11-13 14:06:03
【问题描述】:

我有以下代码。

import yaml

def load_yaml_file(filename):
    with open(filename, 'rt') as f:
        data = yaml.load(f)

    return data

有没有办法模拟 open 部分,以便 f 变为 '{"hello":"world"}',因此我可以断言数据已正确返回。

我尝试使用mock_open.return_value.__enter__.return_value = '{"hello":"world"}' 模拟打开,但无法正常工作。

我正在使用 pytest 和 mocker。

【问题讨论】:

  • 与您的问题无关,但您确定您需要yaml.load() 并且yaml.safe_load() 不会完成这项工作吗?即使您需要从标准 Python 库中加载特定对象,最好为 safe_loading 显式注册这些对象,而不是使用可能不安全的 load()
  • Anthon - 非常非常好的观点,感谢您指出这一点!

标签: python-3.x unit-testing mocking pytest


【解决方案1】:

免责声明

  • 此方案不使用 Mocker,但可以与 pytest 一起使用。
  • 此解决方案适用于 Python >= 3.6

正如您所说,您使用的是 Mocker,我假设您正在使用非常旧的代码库 (= 3.6

由于 Python 3.3 模拟已集成到 unittest.mock 中的标准库,并且几乎是 old mock package 的克隆

mock lib 有一个名为mock_open 的功能,它可以完全满足您的需求,并且有一个完全符合您需求的示例。

with patch('__main__.open', mock_open(read_data='bibble')) as m:
    with open('foo') as h:
        result = h.read()

m.assert_called_once_with('foo')
assert result == 'bibble'

根据您的需要调整解决方案,您可以使用此示例

import yaml
from unittest.mock import patch, mock_open

def load_yaml_file(filename):
    with open(filename, 'rt') as f:
        data = yaml.load(f)

    return data

with patch('__main__.open', mock_open(read_data='{"hello":"world"}')) as m:
    res = load_yaml_file('foo')

assert res == {"hello":"world"}

【讨论】:

    【解决方案2】:

    您根本不需要设置__enter__。只需将您想要读取的数据作为read_data 参数传递给mock_open()

    mocked_open = mock.mock_open(read_data='{"hello":"world"}')
    with mock.patch("yourmodule.open", mocked_open):
        result = load_yaml_file("foobar.yaml")
    

    演示:

    >>> import yaml
    >>> def load_yaml_file(filename):
    ...     with open(filename, 'rt') as f:
    ...         data = yaml.load(f)
    ...     return data
    ...
    >>> from unittest import mock
    >>> mocked_open = mock.mock_open(read_data='{"hello":"world"}')
    >>> with mock.patch("__main__.open", mocked_open):
    ...     result = load_yaml_file("foobar.yaml")
    ...
    >>> print(result)
    {'hello': 'world'}
    >>> mocked_open.mock_calls
    [call('foobar.yaml', 'rt'),
     call().__enter__(),
     call().read(4096),
     call().read(4096),
     call().__exit__(None, None, None)]
    

    【讨论】:

      最近更新 更多