如何模拟超类的__init__方法并创建一个包含模拟对象的属性,以进行单元测试?

12

我正在尝试为一个类的__init__编写单元测试:

def __init__(self, buildNum, configFile = "configfile.txt"):
        super(DevBuild, self).__init__(buildNum, configFile)

        if configFile == "configfile.txt":
            self.config.MakeDevBuild()

config属性是由父类的__init__设置的。我正在使用mock,我希望config属性是一个模拟对象。但是,我一直没有找到如何实现这一点的方法。以下是我为测试想出的最佳方案:

def test_init(self):
        with patch('DevBuild.super', create=True) as mock_super:
            mock_MakeDevBuild = MagicMock()
            mock_super.return_value.config.MakeDevBuild = mock_MakeDevBuild

            # Test with manual configuration
            self.testBuild = DevBuild("42", "devconfigfile.txt")
            self.assertFalse(mock_MakeDevBuild.called)

            # Test with automated configuration
            self.testBuild = DevBuild("42")
            mock_MakeDevBuild.assert_called_once_with()

然而,这并不起作用——我得到了一个错误:

Error
Traceback (most recent call last):
  File "/Users/khagler/Projects/BuildClass/BuildClass/test_devBuild.py", line 17, in test_init
    self.testBuild = DevBuild("42")
  File "/Users/khagler/Projects/BuildClass/BuildClass/DevBuild.py", line 39, in __init__
    self.config.MakeDevBuild()
AttributeError: 'DevBuild' object has no attribute 'config'

显然我没有正确设置配置属性,但是我不知道应该在哪里进行设置。或者说,我想做的事情是否可能。有人能告诉我需要做什么才能让它工作吗?


一个观察:super 的返回值不是一个具有 config 属性的对象,而是一个具有方法 __init__ 的对象,该方法将向其参数添加一个 config 属性。 - chepner
这是整个__init__吗?如果是的话,它只会在没有传递配置文件名时添加self.config.MakeDevBuild,而在您的测试中确实如此。 - jlujan
2个回答

23

您不能直接设置__init__来进行模拟 - 请参见 mock.py中的_unsupported_magics

至于您可以做什么,您可以通过将其传递给patch来模拟__init__,如下所示:

mock_makeDevBuild = MagicMock()
def mock_init(self, buildNum, configFile):
    self.config = MagicMock()
    self.config.MakeDevBuild = mock_makeDevBuild

with patch('DevBuild.SuperDevBuild.__init__', new=mock_init):
    DevBuild("42")
    mock_makeDevBuild.assert_called_once_with()

SuperDevBuildDevBuild的一个基类。

如果你真的想要模拟super(),你可以创建一个类,然后手动将__init__绑定到对象上,例如:

mock_makeDevBuild = MagicMock()
def get_mock_super(tp, obj):
    class mock_super(object):
        @staticmethod
        def __init__(buildNum, configFile):
            obj.config = MagicMock()
            obj.config.MakeDevBuild = mock_makeDevBuild
    return mock_super
with patch('DevBuild.super', create=True, new=get_mock_super):
    DevBuild("42")
    mock_makeDevBuild.assert_called_once_with()

虽然能用,但是相当丑陋。


不幸的是,unsupported_magics的链接已经损坏了。 - AdamC

1
我这样做,是通过模拟继承类的初始化方式来实现的:
    from unittest import mock

    @mock.patch.object(HierarchicalConf, "__init__")
    def test_super_init(self, mock_super_init):
        # act
        ConfigurationService('my_args')

        # assert
        mock_super_init.assert_called_once_with(args)

给定以下类:

class ConfigurationService(HierarchicalConf):
    def __init__(self, dag_name) -> None:
        """Wrapper of Hierarchical Conf."""
        # ... my code    
        super().__init__(args)

如果你也想模拟 ConfigurationService 的初始化,你可以做相同的事情:

    @mock.patch.object(ConfigurationService, "__init__")
    def test_init(self, mock_init):

        # act
        ConfigurationService('my_args')

        # assert
        mock_init.assert_called_once_with('my_args')

这个解决方案确实可行,至少对于Python 3来说是这样。这个答案与被接受的答案相矛盾;另一个关于不支持魔法命令的答案中提供的链接已经失效了。 - Mike C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接