mocker.patch 使用先前参数化的数据 运行

mocker.patch uses data from the previous parametrized run

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

总的来说,我遇到了以下问题:

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

解析:

在第一个 'iteration' 中,在 mark.parametrize 中我使用 mock_data_1 来模拟 GetData.get_data()。然后,测试如我所料在此处模拟 datadata = GetData.get_data() 然后它向数据 data['new_col0'].

添加一个新列

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

这些是一些示例文件:

file.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().copy() 替换 data = GetData.get_data() 可以解决这个问题,但我假设我在测试中做错了。

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

如评论中所述,问题是在测试中使用了全局变量(在本例中为 class 变量,但不会改变行为),在测试内部发生了变化,并且然后在下一个测试中使用更改的变量。没有任何内容告诉 pytest 变量应该被重置 - 重置变量通常在 fixtures 中完成。

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

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中的参数(例如因为参数不是对所有测试都相同),你不能使用夹具,因为装饰器已经在加载时读取时间。在这种情况下 您必须首先自己确保原始参数被重置或未更改,例如像您一样使用副本 - 但在测试而不是生产代码中,您不想更改它:

    @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)
        ...

在 re-reading pytest 文档之后,我发现他们明确指出这是测试的预期行为:

Note:

Parameter values are passed as-is to tests (no copy whatsoever).

For example, if you pass a list or a dict as a parameter value, and the test case code mutates it, the mutations will be reflected in subsequent test case calls.

来源:https://docs.pytest.org/en/6.2.x/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions