简体   繁体   English

PropertyMock 的 side_effect 函数只被调用一次

[英]side_effect function of PropertyMock gets called only once

I have two questions regarding the mocking of a property with unittest.mock.PropertyMock (code will follow below):我有两个关于使用 unittest.mock.PropertyMock 模拟属性的问题(代码如下):

  1. Why does the test test_prop output "text_0, text_0" and not "text_0, text_1" as the test test_func ?为什么测试test_prop输出 "text_0, text_0" 而不是 "text_0, text_1" 作为测试test_func The output indicates, that the function side_effect_func in test_prop gets called only once, while I would have expected it to get called twice, like in test_func .输出表明, test_prop 中的函数side_effect_func test_prop被调用一次,而我预计它会被调用两次,就像在test_func中一样。
  2. How can I specify a side_effect function for a property, that gets called every time the property is accessed?如何为属性指定 side_effect 函数,每次访问该属性时都会调用该函数?

My use case is that I would like to have a mock that returns a different name (which is a property) depending on how often it was called.我的用例是我想要一个根据调用频率返回不同名称(这是一个属性)的模拟。 This would "simulate" two different instances of Class1 to Class2 in the following minimal example.这将在以下最小示例中“模拟” Class1 到 Class2 的两个不同实例。

The code:编码:
File dut.py :文件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

File test\test_dut.py (the second with-statement produces the exact same behavior when swapped with the first one):文件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}')

Call from command line: pytest -rP test\test_dut.py This leads to the following output (problematic line marked by me):从命令行调用: 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 ===============================================================================================

The issue has to do with how you are instantiating the PropertyMock .问题与您如何实例化PropertyMock有关。 To answer your first question about why the second test prints test_0 for both calls, you instantiate the PropertyMock class during your with patch.object(get_events.Class1, 'name', new_callable=PropertyMock(side_effect=side_effect_func)) call.要回答关于为什么第二个测试为两个调用都打印test_0的第一个问题,您在with patch.object(get_events.Class1, 'name', new_callable=PropertyMock(side_effect=side_effect_func))调用期间实例化PropertyMock类。

Since you instantiate the class, it gets called immediately at the end of that line due to the logic of the __enter__ method in the patch object.由于您实例化了该类,因此由于patch对象中__enter__方法的逻辑,它会在该行的末尾立即被调用。 You can see that logic in this line and then this one .您可以在这一行中看到该逻辑,然后在这一看到该逻辑。 Due to that the value of your side_effect immediately becomes a string, which is essentially the output of the first call to the PropertyMock .由于你的side_effect的值立即变成一个字符串,它本质上是第一次调用PropertyMock的输出。 This can be confirmed by changing your function to the following and observing the output:这可以通过将您的函数更改为以下内容并观察输出来确认:

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)

You will notice that this prints text_0 in the console, confirming what has been mentioned above.您会注意到这会在控制台中打印text_0 ,从而确认上面提到的内容。

To answer your second question, the way to use PropertyMock in this case would be to change the second test to the following:要回答您的第二个问题,在这种情况下使用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}')

Then when you run the tests you get the correct output as shown below.然后,当您运行测试时,您会得到正确的输出,如下所示。


============================================================= 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 ==============================================================

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM