如何将Django模拟实例传递给类方法?

如何将Django模拟实例传递给类方法?,django,unit-testing,django-models,mocking,Django,Unit Testing,Django Models,Mocking,模拟测试库是Django的一个主题,我似乎无法理解。例如,在下面的代码中,为什么我在单元测试中创建的模拟用户实例不出现在我在“get\u User\u id”方法中查询的用户对象中?如果通过调试调用在“get_user_id”方法中停止测试并执行“user.objects.all()”,则用户查询集中没有任何内容,测试失败。我不是正在创建三个模拟用户实例,它们将被UserProxy的静态方法查询吗 我正在使用Django 1.6和Postgres 9.3,并使用命令“python manage.

模拟测试库是Django的一个主题,我似乎无法理解。例如,在下面的代码中,为什么我在单元测试中创建的模拟用户实例不出现在我在“get\u User\u id”方法中查询的用户对象中?如果通过调试调用在“get_user_id”方法中停止测试并执行“user.objects.all()”,则用户查询集中没有任何内容,测试失败。我不是正在创建三个模拟用户实例,它们将被UserProxy的静态方法查询吗

我正在使用Django 1.6和Postgres 9.3,并使用命令“python manage.py test-s apps.profile.tests.model_tests:TestUserProxy”运行测试

谢谢

# apps/profile/models.py
from django.contrib.auth.models import User
class UserProxy(User):
    class Meta:
        proxy = True

    @staticmethod
    def get_user_ids(usernames):
        debug()
        user_ids = []
        for name in usernames:
            try:
                u = User.objects.get(username__exact=name)
                user_ids.append(u.id)
            except ObjectDoesNotExist:
                logger.error("We were unable to find '%s' in a list of usernames." % name)
        return user_ids


# apps/profile/tests/model_tests.py
from django.test import TestCase
from django.contrib.auth.models import User
from mock import Mock
from apps.profile.models import UserProxy

class TestUserProxy(TestCase):
    def test_get_user_ids(self):
        u1 = Mock(spec=User)
        u1.id = 1
        u1.username = 'user1'

        u2 = Mock(spec=User)
        u2.id = 2
        u2.username = 'user2'

        u3 = Mock(spec=User)
        u3.id = 3
        u3.username = 'user3'

        usernames = [u1.username, u2.username, u3.username]
        expected = [u1.id, u2.id, u3.id]
        actual = UserProxy.get_user_ids(usernames)
        self.assertEqual(expected, actual)

模拟对于测试来说是非常棒的,并且可以导致非常干净的测试,但是它有一点困难:(a)在开始时有点难以理解,(b)经常需要一些努力来设置模拟对象,然后在正确的位置注入/使用

您为用户创建的模拟对象是看起来像Django
User
model对象的对象,但它们不是实际的模型对象,因此不会被放入数据库中

要使测试正常工作,您有两个选项,具体取决于您想要编写的测试类型

单元测试-模拟从数据库返回的数据

第一个选项是将其作为单元测试来工作,即测试
get\u user\u id
方法,与数据库层隔离。为此,您需要模拟对
User.objects.get(username\uu exact=name)
的调用,以便它返回您在测试中创建的三个模拟对象。这将是更正确的方法(因为最好单独测试代码单元),但是它将比下面的备选方案涉及更多的设置工作

实现这一点的一种方法是首先在apps/profile/models.py中将用户查找分离为自己的功能:

这需要在函数中调用,方法是将对
Users.objects.get(username\uu exact=name)
的调用替换为
get\u Users\u by\u name(name)
。然后,您可以修改测试以修补函数,如下所示:

from django.test import TestCase
from django.contrib.auth.models import User
from mock import Mock, patch
from apps.profile.models import UserProxy

class TestUserProxy(TestCase):

    @patch('apps.profile.models.get_user_by_name')
    def test_get_user_ids(self, mock_get_user_by_name):
        u1 = Mock(spec=User)
        u1.id = 1
        u1.username = 'user1'

        u2 = Mock(spec=User)
        u2.id = 2
        u2.username = 'user2'

        u3 = Mock(spec=User)
        u3.id = 3
        u3.username = 'user3'

        # Here is where we wire up the mocking - we take the patched method to return
        # users and tell it that, when it is called, it must return the three mock
        # users you just created.
        mock_get_user_by_name.return_value = [u1, u2, u3]

        usernames = [u1.username, u2.username, u3.username]
        expected = [u1.id, u2.id, u3.id]
        actual = UserProxy.get_user_ids(usernames)
        self.assertEqual(expected, actual)
集成测试-创建真实用户对象

第二种方法是将其修改为集成测试,即测试此代码单元以及与数据库的交互。这有点不干净,因为您现在将方法上的测试暴露在失败的可能性中,因为不同的代码单元(即与数据库交互的Django代码)中存在问题。然而,这确实使测试的设置简单得多,从实用角度来看,这可能是适合您的正确方法


为此,只需删除您创建的模拟,并在数据库中创建实际用户,作为测试的一部分。

谢谢您的努力。虽然您的第一种方法很有启发性,但我认为这不是我所寻求的解决方案,因为它的重点是生成模拟用户来填充用户名列表,而不是创建一个实际的模拟用户对象,该对象可以通过get_User_id方法访问。你的第二个答案更接近事实,也是我给你评分的原因。然而,我没有创建一个用户模型实例,而是使用factory_boy及其创建策略(因为我需要用户ID)来创建一个对所讨论的方法可见的用户实例。我认为您做出了正确的选择-第一个选项是纯粹的方法,第二个选项更实用,也是我将采用的方法。使用mock是非常强大的,但是对于这个特定的用例,使用真实的用户对象将导致更简单、更可读的测试。您希望创建真实用户而不是模拟:您需要通过
user()
创建真实对象,如中所示<代码>模拟()是模拟,而不是包装。你应该把你的问题改清楚。。。两个as误解了你想要什么。。。
from django.test import TestCase
from django.contrib.auth.models import User
from mock import Mock, patch
from apps.profile.models import UserProxy

class TestUserProxy(TestCase):

    @patch('apps.profile.models.get_user_by_name')
    def test_get_user_ids(self, mock_get_user_by_name):
        u1 = Mock(spec=User)
        u1.id = 1
        u1.username = 'user1'

        u2 = Mock(spec=User)
        u2.id = 2
        u2.username = 'user2'

        u3 = Mock(spec=User)
        u3.id = 3
        u3.username = 'user3'

        # Here is where we wire up the mocking - we take the patched method to return
        # users and tell it that, when it is called, it must return the three mock
        # users you just created.
        mock_get_user_by_name.return_value = [u1, u2, u3]

        usernames = [u1.username, u2.username, u3.username]
        expected = [u1.id, u2.id, u3.id]
        actual = UserProxy.get_user_ids(usernames)
        self.assertEqual(expected, actual)