2

我的理解是,autospec这里使用的最简单的形式将检查正在模拟的函数的签名与提供的参数。其目的是在它们不匹配时引发错误。在下面的代码中,它似乎注入了一个额外的参数——对象本身。为什么使用模拟模块会autospec导致此处显示的意外行为?对于这个问题,我在模块中创建了一个简化版本simplebutton。当它作为主模块运行时,会打印“这不是开玩笑”这一行。

#module simplebutton
import sys


class _Dialog2:
    def callback(self):
        print("It's no joke")


def main():
    dialog = _Dialog2()
    dialog.callback()


if __name__ == '__main__':
    sys.exit(main())

测试模块test_simplebutton包含两个测试都有效。两者都模拟了该callback功能。然而,第二个测试包括autospec=True.

    @unittest.mock.patch('simplebutton._Dialog2.callback',
                         name='callback', autospec=True)

在这个测试中,应该不带参数调用的回调函数必须用参数调用,dialog否则测试失败。

编辑: 每个人都知道您不是通过调用方法method(instance)而是通过调用方法instance.method()。那是我的错误。在这里,它需要是模拟在instance1.method('instance2')哪里,并且是包含模拟方法的对象。感谢 Michele d'Amico。instance1instance2

        mock_callback.assert_called_once_with(dialog)    

测试套件如下:

#module test_simplebutton
import unittest
import unittest.mock

import simplebutton


class Test_Dialog(unittest.TestCase):

    @unittest.mock.patch('simplebutton._Dialog2.callback',
                         name='callback')
    def test_direct_call_to_callback_by_mocking_1(self, mock_callback):
        dialog = simplebutton._Dialog2()
        dialog.callback()
        mock_callback.assert_called_once_with()

    @unittest.mock.patch('simplebutton._Dialog2.callback',
                         name='callback', autospec=True)
    def test_direct_call_to_callback_by_mocking_2(self, mock_callback):
        dialog = simplebutton._Dialog2()
        dialog.callback()
        mock_callback.assert_called_once_with(dialog)
4

1 回答 1

3

通过使用具有相同原始对象签名的模拟autospec=True补丁替换对象(在您的情况下为方法)。此外,生成的模拟无法扩展:尝试访问不在原始定义(或MagicMock())中的属性或方法将引发异常。

在第一种情况下(没有autospec=True),您正在通过无界方法修补绑定方法。当您调用修补方法时,mock_callback它被称为函数而不是dialog对象的绑定方法。

当你autospec=True@patch装饰器中使用它时,它会用一个新的绑定方法替换原来的方法mock_callback:这就像所有其他绑定方法一样,并将作为第一个参数被 self 调用。为了使示例更清晰,我对其进行了更改以更好地解释autospec=True补丁参数的行为。

import unittest
import unittest.mock

import simplebutton

class Test_Dialog(unittest.TestCase):

    @unittest.mock.patch('simplebutton._Dialog2.callback')
    def test_direct_call_to_callback_by_mocking_1(self, mock_callback):
        dialog = simplebutton._Dialog2()
        dialog.callback()
        mock_callback.assert_called_once_with()
        mock_callback.reset_mock()
        simplebutton._Dialog2.callback()
        mock_callback.assert_called_once_with()

    @unittest.mock.patch('simplebutton._Dialog2.callback', autospec=True)
    def test_direct_call_to_callback_by_mocking_2(self, mock_callback):
        dialog = simplebutton._Dialog2()
        dialog.callback()
        mock_callback.assert_called_once_with(dialog)
        self.assertRaises(Exception, simplebutton._Dialog2.callback)

        dialog2 = simplebutton._Dialog2()
        dialog.callback()
        dialog2.callback()
        mock_callback.assert_has_calls([unittest.mock.call(dialog), unittest.mock.call(dialog2)])

在第一个测试中,我们要么显式调用_Dialog2.callback()as 未绑定方法 bysimplebutton._Dialog2.callback()并且行为与dialog.callback().

在第二个中,如果我们尝试像第一个测试一样将其称为未绑定,它将引发异常。此外,如果我们从两个不同的对象调用该方法,我们会发现对同一个模拟的两个不同调用,我们可以识别它们。

我希望现在清楚发生了什么以及使用autospec=True参数时应该期待什么。

于 2015-01-11T21:19:15.310 回答