如何模拟超类__init__创建包含单元测试模拟对象的属性?



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

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

config属性由超级服务器的__init__设置。我正在使用mock,并且我希望配置属性是一个mock对象。然而,我还不知道如何才能真正实现这一点。以下是我能想到的最好的测试:

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'

显然我没有正确设置config属性,但我不知道我应该在哪里设置它。或者说,如果我想做的事情是可能的。有谁能告诉我,我需要做些什么才能做到这一点吗?

您不能通过直接设置来模拟__init__ -参见mock.py中的_unsupported_magics。

您可以通过将__init__传递给patch来模拟它,如下所示:

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()

其中SuperDevBuild是DevBuild的基类

如果您真的想模拟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()

我这样做,模仿继承类init:

    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 init,你可以做同样的事情:

    @mock.patch.object(ConfigurationService, "__init__")
    def test_init(self, mock_init):
        # act
        ConfigurationService('my_args')
        # assert
        mock_init.assert_called_once_with('my_args')