【问题标题】:How to properly mock private members of a class如何正确模拟班级的私人成员
【发布时间】:2019-04-04 09:41:56
【问题描述】:

我正在尝试为依赖于另一个私有方法的方法编写一些单元测试。 - 如下例所示:

def is_member_of(self, group_name):
  members = self.__get_group_members(group_name)

我想模拟的私有方法是__get_group_members;我还想模拟私有属性__user_id,因为它将在is_member_of 函数中使用(上面的示例中未显示)。

到目前为止我所拥有的:

import unittest
from unittest import mock

class Test(unittest.TestCase):
  group_data = []
  user_id = 'test_user_id'

  def mock_dependencies(self, x):
    x.__user_id = mock.PropertyMock(return_value=self.user_id)
    x.__get_group_members = mock.MagicMock(return_value=self.group_data)

  def first_test(self):
    x = A(('name', 'group'))
    self.mock_dependencies(x)
    x.is_member_of('test_group')

当我调用x.is_member_of() 时,模拟没有按预期工作。

【问题讨论】:

    标签: python python-3.x python-unittest


    【解决方案1】:

    您可以在 Python 中访问私有属性,因为 private 和 protected 是约定俗成的。 - 您正在寻找的基本上是使用 _ClassName__private_attribute_name 因为,python 执行重命名以实现约定的约定。

    示例(返回 MagicMock):

    with mock.patch.object(Class, '_ClassName__private_attribute_name', return_value='value') as obj_mock:
        pass
    

    示例(返回原始值):

    with mock.patch.object(Class, '_ClassName__private_attribute_name', new_callable=PropertyMock) as obj_mock:
        obj_mock.return_value = 'string value'
    

    Class 是对类本身的引用,而不是对实例的引用。

    完整示例:

    from unittest.mock import patch, PropertyMock
    from unittest import TestCase, main
    
    
    class Private:
        __attr = 'hello'
    
    
    class PrivateTest(TestCase):
        @patch.object(Private, '_Private__attr', new_callable=PropertyMock)
        def test_private_attribute_value_change_decorator_success(self, private_mock):
            obj = Private()
            private_mock.return_value = 'string'
    
            self.assertEqual('string', obj._Private__attr)
    
        def test_private_attribute_value_change_context_manager_success(self):
            with patch.object(Private, '_Private__attr', new_callable=PropertyMock) as o_mock:
                obj = Private()
    
                o_mock.return_value = 'mocked value'
                self.assertEqual('mocked value', obj._Private__attr)
    
    
    if __name__ == '__main__':
        main()
    

    对示例的修改:

    from unittest import TestCase, mock, main
    
    
    class A:
        __user_id = 3
    
        def __init__(self, user, group):
            """
            Your logic is missing - obviously
            :param user:
            :param group:
            """
    
    
        def __get_group_members(self):
            """
            Your logic is missing - obviously
            :return:
            """
            return ['user_1', 'user_2']
    
        def is_member_of(self, group_name):
            members = self.__get_group_members(group_name)
    
            # will return if the user is a member of the group
            return self.__user_id in members
    
    
    class GroupTest(TestCase):
        group_data = [1, 2]
        user_id = 'test_user_id'
    
        @mock.patch.object(A, '_A__get_group_members')
        @mock.patch.object(A, '_A__user_id', new_callable=mock.PropertyMock)
        def test_this_is_my_first_success(self, user_id_mock: mock.PropertyMock, get_group_members_mock: mock.MagicMock):
            get_group_members_mock.return_value = self.group_data
            user_id_mock.return_value = 3
    
            x = A('user_3', 'this_group')
            self.assertEqual(False, x.is_member_of('test_group'))
    
        @mock.patch.object(A, '_A__get_group_members')
        @mock.patch.object(A, '_A__user_id', new_callable=mock.PropertyMock)
        def test_this_is_my_first_failure(self, user_id_mock: mock.PropertyMock, get_group_members_mock: mock.MagicMock):
            get_group_members_mock.return_value = self.group_data
            user_id_mock.return_value = 1
    
            x = A('user_1', 'this_group')
            self.assertEqual(True, x.is_member_of('test_group'))
    
    
    if __name__ == '__main__':
        main()
    

    如果你知道你会在所有测试用例中模拟这两个属性,你可以在类级别添加装饰器并期望同样的参数。

    如果 属性是通过__init__ 或任何其他方法设置的,您可以简单地更改它,如下所示。

    from unittest import TestCase, mock, main
    
    
    class A:
        def __init__(self, user, group):
            """
            Your logic is missing - obviously
            :param user:
            :param group:
            """
    
    
        def __get_group_members(self):
            """
            Your logic is missing - obviously
            :return:
            """
            return ['user_1', 'user_2']
    
        def is_member_of(self, group_name):
            members = self.__get_group_members(group_name)
    
            # will return if the user is a member of the group
            return self.__user_id in members
    
    
    class GroupTest(TestCase):
        group_data = [1, 2]
        user_id = 'test_user_id'
    
        @mock.patch.object(A, '_A__get_group_members')
        def test_this_is_my_first_success(self, get_group_members_mock: mock.MagicMock):
            x = A('user_3', 'this_group')
    
            x._A__user_id = 5
            get_group_members_mock.return_value = self.group_data
    
            self.assertEqual(False, x.is_member_of('test_group'))
    
        @mock.patch.object(A, '_A__get_group_members')
        def test_this_is_my_first_failure(self, get_group_members_mock: mock.MagicMock):
            get_group_members_mock.return_value = self.group_data
    
            x = A('user_1', 'this_group')
            x._A__user_id = 1
            self.assertEqual(True, x.is_member_of('test_group'))
    
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

    • 当试图访问成员时,“obj.__private_attribute_name”给出了<MagicMock name='_ClassName__private_attribute_name' id='63191920'>,由于某种原因,它不是指定的返回值('value')。我该如何解决这个问题?
    • 您正在尝试使用PropertyMock - 已更新答案。
    • 这次得到了`'。在 return_value 赋值之后打印“obj_mock.__private_attribute_name”
    • 让我给你查一下。
    • 感谢您的帮助
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-23
    • 1970-01-01
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    • 2019-08-28
    相关资源
    最近更新 更多