使用补丁模拟两个函数进行单元测试

cno*_*ell 59 python testing unit-testing mocking nose

我有一个函数我要单元测试包含调用其他两个函数.我不确定如何使用补丁同时模拟两个函数.我在下面提供了一个我的意思的例子.当我运行测试时,测试通过,但我觉得必须有一个更清洁的方法来做到这一点,我真的不明白有关f.close()的部分...

目录结构如下所示:

program/
  program/
    data.py
  tests/
    data_test.py
Run Code Online (Sandbox Code Playgroud)

data.py:

import cPickle

def write_out(file_path, data):
    f = open(file_path, 'wb')
    cPickle.dump(data, f)
    f.close()
Run Code Online (Sandbox Code Playgroud)

data_test.py:

from mock import MagicMock, patch

def test_write_out():
    path = '~/collection'
    mock_open = MagicMock()
    mock_pickle = MagicMock()
    f_mock = MagicMock()
    with patch('__builtin__.open', mock_open):
        f = mock_open.return_value
        f.method.return_value = path
        with patch('cPickle.dump', mock_pickle):
            write_out(path, 'data')
            mock_open.assert_called_once_with('~/collection', 'wb')
            f.close.assert_any_call()
            mock_pickle.assert_called_once_with('data', f)
Run Code Online (Sandbox Code Playgroud)

结果:

$ nosetests
.
----------------------------------------------------------------------
Ran 1 test in 0.008s
OK
Run Code Online (Sandbox Code Playgroud)

Mat*_*ohn 90

您可以使用修补程序装饰器简化测试并将它们嵌套(MagicMock默认情况下它们是对象):

@patch('cPickle.dump')
@patch('__builtin__.open')
def test_write_out(mock_open, mock_pickle):
    path = '~/collection'
    f = mock_open.return_value
    f.method.return_value = path

    write_out(path, 'data')

    mock_open.assert_called_once_with('~/collection', 'wb')
    mock_pickle.assert_called_once_with('data', f)
    f.close.assert_any_call()
Run Code Online (Sandbox Code Playgroud)

MagicMock实例的调用返回一个新MagicMock实例,因此您可以检查返回的值是否像任何其他模拟对象一样被调用.在这种情况下f是一个MagicMock命名'open()'(尝试打印f).

  • 装饰器自下而上应用,参数的顺序需要与之匹配.见这里:http://www.voidspace.org.uk/python/mock/patch.html?highlight = stack#nesting-patch-decorators (33认同)
  • 在您的建议中,您引入了两个参数,每个模拟一个参数.python如何知道哪个是哪个?我没能在文档中找到答案. (6认同)
  • 请注意,此处的参数与补丁的应用顺序相反。 (3认同)

Ale*_*voy 39

除了响应@Matti John之外,您还可以使用patch内部函数test_write_out:

from mock import MagicMock, patch

def test_write_out():
    path = '~/collection'
    with patch('__builtin__.open') as mock_open, \
            patch('cPickle.dump') as mock_pickle:

        f = mock_open.return_value
        ...
Run Code Online (Sandbox Code Playgroud)


Mar*_*arc 16

从 Python 3.10 开始,您可以像这样使用带括号的上下文管理器

from unittest.mock import patch


def test_write_out():
    with (
        patch('cPickle.dump'),
        patch('__builtin__.open') as open_mock,  # example of using `as`
    ):
        yield

Run Code Online (Sandbox Code Playgroud)