如何将数据传递给pytest中的monkeypatch

How to pass data to a monkeypatch in pytest

我有一个简单的测试,我想测试用户数据的正确创建。 Data 的创建取决于用户的周数(可以认为是加入的一周)。这意味着具有 week=2 的用户应该拥有所有 Data 个对象,这些对象也具有 week=2。由于我有不同周的用户,我希望我的测试涵盖几个星期。我的问题是 User.week 是一个 属性,它使用计算来获得真实的周数,我不知道如何将 week 参数传递给我的 monkeypatch fixture 以生成静态值是“动态的”。

现在我有了一个适合我的丑陋解决方案 - 我创建了两个具有不同 return 值的相同模拟。

test_tasks.py

from users.tasks import create_default_data_for_user

class TestUserTasks:

    @pytest.mark.parametrize(
        "week",
        ["mock_user_week_1", "mock_user_week_2"]
    )
    def test_create_default_data_for_user(self, user, rf, data, week):
        """
        Check creation of user data using create_default_data_for_user task.

        :param user: a user object fixture
        :param rf: instance of Django test RequestFactory
        :param data: fixture that creates default data object
        :param week: mock object of user's week property
        :return:
        """

        # Make sure the user has no user data
        assert len(user.data_set.all()) == 0

        create_default_data_for_user()

        # Now the user should be given data according to his week
        expected_user_data_count = Data.objects.filter(week=user.week).count()

        assert len(user.data_set.all()) == expected_user_data_count

        # Make sure that for the current user the second run will not cause user data duplication
        create_default_data_for_user()
        assert len(user.data_set.all()) == expected_user_data_count

fixtures.py

import pytest

@pytest.fixture
def mock_user_week_1(monkeypatch):
    """
    Mock the User object's property week.

    The mock object will prevent calculation of the real User's week
    and just return a given number.
    """

    @property
    def user_week(*args, **kwargs):
        """
        A mock object that overrides the method and returns a static value.

        :param args: args of the original object
        :param kwargs: kwargs of the original object
        :return: static value indicating week number 1
        """
        return 1

    monkeypatch.setattr(
        User,
        "week",
        user_week
    )


@pytest.fixture
def mock_user_week_2(monkeypatch):
    """
    Mock the User object's property week.

    The mock object will prevent calculation of the real User's week
    and just return a given number.
    """

    # pylint: disable=unused-argument
    @property
    def user_week(*args, **kwargs):
        """
        A mock object that overrides the method and returns a static value.

        :param args: args of the original object
        :param kwargs: kwargs of the original object
        :return: static value indicating week number 2
        """
        return 2

    monkeypatch.setattr(
        User,
        "week",
        user_week
    )

终于,我明白了。 结果证明解决方案很简单:

fixtures.py

@pytest.fixture
def mock_user_week(monkeypatch, week_number):
    """
    Mock the User object's method week.

    The mock object will prevent calculation of the real User's week
    and just return a given number.
    """

    @property
    def user_week(*args, **kwargs):
        """
        A mock object that overrides the method and return static value.

        :param args: args of the original object
        :param kwargs: kwargs of the original object
        :return: static value indicating week number
        """

        return week_number

    monkeypatch.setattr(
        User,
        "week_in_program",
        user_week_in_program
    )

test_tasks.py

from users.tasks import create_default_data_for_user

class TestUserTasks:

    @pytest.mark.parametrize(
        "week_number",
        [1, 2, 3, 4, 5]
    )
    def test_create_default_data_for_user(self, user, rf, data, week_number, mock_user_week):
        """
        Check creation of user data using create_default_data_for_user task.

        :param user: a user object fixture
        :param rf: instance of Django test RequestFactory
        :param data: fixture that creates default data object
        :param int week_number: user's week to be used
        :param mock_user_week: mock object of user's week property
        :return:
        """

        # Make sure the user has no user data
        assert len(user.data_set.all()) == 0

        create_default_data_for_user()

        # Now the user should be given data according to his week
        expected_user_data_count = Data.objects.filter(week=user.week).count()

        assert len(user.data_set.all()) == expected_user_data_count

        # Make sure that for the current user the second run will not cause user data duplication
        create_default_data_for_user()
        assert len(user.data_set.all()) == expected_user_data_count

这里的“魔法”是我在参数化、测试方法参数和模拟夹具中使用参数 week_number。因此 pytest 将此参数传递给测试方法和模拟夹具。在这种特殊情况下,每个 parametrize 运行 都会从模拟夹具中产生一个新的 return 值。在测试中,我正在为给定列表的不同周创建 Data 个对象。 我什至无法认为 pytest 本身将参数传递给固定装置而无需额外的“设置”。

如果文档中有这样的东西就好了。