python - PropertyMock 的 side_effect 函数仅被调用一次

标签 python pytest python-unittest.mock

我有两个关于使用unittest.mock.PropertyMock模拟属性的问题(代码如下):

  1. 为什么测试 test_prop 输出“text_0, text_0”而不是“text_0, text_1”作为测试 test_func?输出表明,test_prop 中的函数 side_effect_func 只被调用一次,而我预计它会被调用两次,就像在 test_func 中一样.
  2. 如何为属性指定 side_effect 函数,每次访问该属性时都会调用该函数?

我的用例是我想要一个模拟,它根据调用的频率返回不同的名称(这是一个属性)。这将在下面的最小示例中“模拟”Class1 到 Class2 的两个不同实例。

代码:
文件dut.py:

class Class1():
    def __init__(self, name):
        self.__name = name

    @property
    def name(self):
        return self.__name

    def name_func(self):
        return self.__name


class Class2():
    def __init__(self, name, class1):
        self.__name = name
        self.__class1 = class1

    @property
    def name(self):
        return self.__name
    @property
    def class1(self):
        return self.__class1

文件test\test_dut.py(第二个with语句与第一个语句交换时产生完全相同的行为):

import dut
import unittest
from unittest.mock import patch, PropertyMock

class TestClass2(unittest.TestCase):
    def test_func(self):
        side_effect_counter = -1
        def side_effect_func(_):
            nonlocal side_effect_counter
            side_effect_counter += 1
            return f'text_{side_effect_counter}'

        c2_1 = dut.Class2('class2',  dut.Class1('class1'))
        c2_2 = dut.Class2('class2_2', dut.Class1('class1_2'))
        with patch('test_dut.dut.Class1.name_func', side_effect=side_effect_func, autospec=True):
            print(f'{c2_2.class1.name_func()}, {c2_1.class1.name_func()}')

    def test_prop(self):
        side_effect_counter = -1
        def side_effect_func():
            nonlocal side_effect_counter
            side_effect_counter += 1
            return f'text_{side_effect_counter}'

        c2_1 = dut.Class2('class2',  dut.Class1('class1'))
        c2_2 = dut.Class2('class2_2', dut.Class1('class1_2'))
        with patch.object(dut.Class1, 'name', new_callable=PropertyMock(side_effect=side_effect_func)):
        # with patch('test_dut.dut.Class1.name', new_callable=PropertyMock(side_effect=side_effect_func)):
            print(f'{c2_2.class1.name}, {c2_1.class1.name}')

从命令行调用:pytest -rP test\test_dut.py 这会导致以下输出(我标记的有问题的行):

============================================================================================== test session starts ==============================================================================================
platform win32 -- Python 3.9.12, pytest-7.1.2, pluggy-1.0.0
rootdir: C:\Users\klosemic\Documents\playground_mocks
plugins: hypothesis-6.46.5, cov-3.0.0, forked-1.4.0, html-3.1.1, metadata-2.0.1, xdist-2.5.0
collected 2 items

test\test_dut.py ..                                                                                                                                                                                        [100%]

==================================================================================================== PASSES =====================================================================================================
_____________________________________________________________________________________________ TestClass2.test_func ______________________________________________________________________________________________
--------------------------------------------------------------------------------------------- Captured stdout call ----------------------------------------------------------------------------------------------
text_0, text_1
_____________________________________________________________________________________________ TestClass2.test_prop ______________________________________________________________________________________________
--------------------------------------------------------------------------------------------- Captured stdout call ----------------------------------------------------------------------------------------------
text_0, text_0 <<<<<< HERE IS THE PROBLEM
=============================================================================================== 2 passed in 0.46s ===============================================================================================

最佳答案

该问题与如何实例化PropertyMock有关。要回答关于为什么第二个测试为两个调用都打印 test_0 的第一个问题,您可以在 期间使用 patch.object(get_events.Class1, ' 实例化 PropertyMock 类name', new_callable=PropertyMock(side_effect=side_effect_func)) 调用。

自从实例化该类后,由于 patch 中的 __enter__ 方法的逻辑,它会在该行的末尾立即被调用> 对象。您可以在 this 中看到该逻辑。行然后这个 one 。因此,side_effect 的值立即变成一个字符串,它本质上是第一次调用 PropertyMock 的输出。可以通过将函数更改为以下内容并观察输出来确认这一点:

with patch.object(dut.Class1, 'name', new_callable=PropertyMock(side_effect=side_effect_func)) as mock_prop:
    # print(f'{c2_2.class1.name}, {c2_1.class1.name}')
    print(mock_prop)

您会注意到这会在控制台中打印 text_0,确认上面提到的内容。

要回答你的第二个问题,在这种情况下使用 PropertyMock 的方法是将第二个测试更改为以下内容:

with patch.object(dut.Class1, 'name', new_callable=PropertyMock) as mock_prop:
    mock_prop.side_effect = side_effect_func
    print(f'{c2_2.class1.name}, {c2_1.class1.name}')

然后,当您运行测试时,您会得到正确的输出,如下所示。


============================================================= test session starts =============================================================
platform darwin -- Python 3.8.9, pytest-7.0.1, pluggy-1.0.0
rootdir: ***
plugins: asyncio-0.18.3, mock-3.7.0
asyncio: mode=strict
collected 2 items                                                                                                                             

tests/test_dut.py ..                                                                                                                 [100%]

=================================================================== PASSES ====================================================================
____________________________________________________________ TestClass2.test_func _____________________________________________________________
------------------------------------------------------------ Captured stdout call -------------------------------------------------------------
text_0, text_1
____________________________________________________________ TestClass2.test_prop _____________________________________________________________
------------------------------------------------------------ Captured stdout call -------------------------------------------------------------
text_0, text_1
============================================================== 2 passed in 0.01s ==============================================================

关于python - PropertyMock 的 side_effect 函数仅被调用一次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72742054/

相关文章:

python - 在 keras 层中包装 tensorflow 函数

python - 使用 pandas python 中另一个数据框中另一列的索引更新数据框中的列

python - 如何使用参数上的自定义标记在 pytest 中选择测试子集

pytest - 模拟雪花连接

python - 如何在python中对数据库连接pymysql进行单元测试?

python - 如果 Pandas 从多列返回值等于另一列中的值

python - ruamel.yaml - 缩进序列不起作用?

python - 如何测试单例 __del__() 方法?

python - 如何使用每个模块的测试数据初始化数据库? Pytest-django