如何正确嘲笑班级的私人成员

时间:2019-04-04 09:41:57

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

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

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()时,模拟无法按预期进行。

1 个答案:

答案 0 :(得分:3)

由于 private和protected是按惯例进行的,因此您可以在Python中访问private属性。-您要查找的基本上是使用_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()