用补丁模拟两个功能进行单元测试


78

我有一个要单元测试的功能,其中包含调用其他两个功能的功能。我不确定如何使用补丁同时模拟两个函数。我在下面提供了我的意思的示例。当我进行鼻子测试时,测试通过了,但是我觉得必须有一种更清洁的方法来进行测试,而我实际上并不了解有关f.close()的内容。

目录结构如下所示:

program/
  program/
    data.py
  tests/
    data_test.py

data.py:

import cPickle

def write_out(file_path, data):
    f = open(file_path, 'wb')
    cPickle.dump(data, f)
    f.close()

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)

结果:

$ nosetests
.
----------------------------------------------------------------------
Ran 1 test in 0.008s
OK

1
我认为我最初的问题不清楚,因此我将其清理干净。我希望这能更准确地显示我正在寻找的东西!
cnodell

Answers:


110

您可以通过使用补丁装饰器并像这样嵌套它们来简化测试(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()

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


8
在您的建议中,您引入了两个参数,每个参数一个。python如何知道哪个是哪个?我未能在文档中找到答案。
步骤

49
装饰器是自下而上应用的,参数的顺序需要与此匹配。在这里看到:voidspace.org.uk/python/mock/...
马蒂·约翰·

1
是的,我已经读过这篇文章,但是还不太清楚。谢谢!
步骤

4
请注意,此处的参数与补丁的应用顺序相反。
Shubham Chaudhary 2015年

优秀!这次真是万分感谢。
R Claven

55

除了响应@Matti John之外,您还可以使用patchinside函数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
        ...

2

这里有一个简单的例子,如何提高测试ConflictErrorcreate_collection使用模拟功能:

import os
from unittest import TestCase
from mock import patch
from ..program.data import ConflictError, create_collection


class TestCreateCollection(TestCase):
    def test_path_exists(self):
        with patch.object(os.path, 'exists') as mock_method:
            mock_method.return_value = True

            self.assertRaises(ConflictError, create_collection, 'test')

请也请参阅模拟文档和Michael Foord对模拟的精彩介绍


谢谢您甚至尝试帮助我。这确实对我有帮助,但是我更专注于如何使用补丁模拟多个功能。不幸的是,我的问题并未明确。我现在已经清理了问题。
cnodell
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.