【问题标题】:mocker.patch uses data from the previous parametrized runmocker.patch 使用来自先前参数化运行的数据
【发布时间】:2021-10-05 15:44:02
【问题描述】:

我是 pytest 的新手,所以我可能会错误地使用一些 pytest 语义。

一般来说,我有以下问题:

我在测试中使用 mark.parametrize 进行模拟,当我在参数中使用相同的变量时,模拟使用的是上次运行的数据,而不是我指定的数据。

分析:

在第一次“迭代”中,在 mark.parametrize 中,我使用 mock_data_1 来模拟 GetData.get_data()。然后,我期望的测试在这里模拟datadata = GetData.get_data(),然后向数据data['new_col0'] 添加一个新列。

在第二次“迭代”中,我在 mark.parametrize 中再次使用 mock_data_1,而不是使用一组新的新 mock_data_1,测试使用以前的数据,包含额外的列。

这些是一些示例文件:

文件.py

from test_file_get_data import GetData

class MyClass:
    def new_dataset(arg):
        data = GetData.get_data(arg)  # Mock this part
        data[f'new_col{arg}'] = arg  # New column to data
        return data

test_file.py

from file import MyClass
import pandas as pd
import pytest

class TestMyClass:
    mock_data_1 = pd.DataFrame({"col_1": [1,2,3]})
    arg_1 = 0
    arg_2 = 1
    output_1 = pd.DataFrame({"col_1": [1,2,3], "new_col0": [0,0,0]})
    output_2 = pd.DataFrame({"col_1": [1,2,3], "new_col1": [1,1,1]})

    @pytest.mark.parametrize(
        'mock_arguments, arg, result',
        [
            (mock_data_1, arg_1, output_1),
            (mock_data_1, arg_2, output_2)
        ]
    )
    def test_new_dataset(self, mocker, mock_arguments, arg, result):
       mocker.patch(
            'file.GetData.get_data',
            return_value=mock_arguments,
        )
       print(mock_arguments)
       res = MyClass.new_dataset(arg)
       print(res)
       assert res.to_dict() == result.to_dict()

test_file_get_data.py

import pandas as pd

class GetData:
    def get_data(arg):
        data = pd.DataFrame({"a":[1, 2, 3]})
        return data

所以第一个测试通过了,但是第二个测试失败了,因为返回的数据是这样的:

{'col_1': {1, 2, 3},
 'new_col0': {0, 0, 0},
 'new_col1': {1, 1, 1}}

而不是这个:

{'col_1': {1, 2, 3},
 'new_col1': {1, 1, 1}}

如果我将data = GetData.get_data() 替换为data = GetData.get_data().copy(),则可以解决此问题,但我假设我在测试中做错了什么。

每次迭代后不应该刷新和/或删除数据吗? 或者正在发生的事情是预期的行为?

【问题讨论】:

  • 你写的代码没有运行,请更正你的例子
  • 除了代码无法按原样运行之外,您并没有展示GetData 的实际作用。我的猜测是它返回一个全局字典,每次调用都会对其进行操作,因此行为符合预期。
  • 抱歉,我现在添加了一个可重现的示例。

标签: python pandas pytest pytest-mock


【解决方案1】:

正如评论中提到的,问题是在测试中使用了一个全局变量(在这种情况下是一个类变量,但不会改变行为),在测试内部进行了更改,然后使用了更改后的变量在接下来的测试中。没有什么可以告诉pytest 变量应该被重置 - 重置变量通常在夹具中完成。

如果像在您的示例中一样,参数在测试中没有更改,则您根本不必将其添加为参数。在这种情况下,您可以使用在每个测试中重置的夹具:

class TestMyClass:

    arg_1 = 0
    arg_2 = 1
    output_1 = pd.DataFrame({"col_1": [1, 2, 3], "new_col0": [0, 0, 0]})
    output_2 = pd.DataFrame({"col_1": [1, 2, 3], "new_col1": [1, 1, 1]})

    @pytest.fixture
    def mock_arguments(self):
        return pd.DataFrame({"col_1": [1, 2, 3]})


    @pytest.mark.parametrize(
        'arg, result',
        [
            (arg_1, output_1),
            (arg_2, output_2)
        ]
    )
    def test_new_dataset(self, mocker, mock_arguments, arg, result):
        mocker.patch(
            'file.GetData.get_data',
            return_value=mock_arguments,
        )
        print(mock_arguments)
        ...

这是在pytest 中处理变量重置的标准方法。

如果你想像你一样使用parametrize 中的参数(例如,因为参数并非对所有测试都相同),你不能使用fixture,因为在加载时已经读取了装饰器。在这种情况下 您必须确保自己首先重置或不更改原始参数,例如像您一样使用副本 - 但在测试而不是您不想更改的生产代码中:

    @pytest.mark.parametrize(
        'mock_arguments, arg, result',
        [
            (mock_data_1, arg_1, output_1),
            (mock_data_1, arg_2, output_2)
        ]
    )
    def test_new_dataset(self, mocker, mock_arguments, arg, result):
        mocker.patch(
            'file.GetData.get_data',
            return_value=mock_arguments.copy(),  # use a copy of the value
        )
        print(mock_arguments)
        ...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-10
    • 1970-01-01
    • 2011-10-05
    • 1970-01-01
    • 2021-10-02
    • 2018-02-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多