我有两个关于使用unittest.mock.PropertyMock模拟属性的问题(代码如下):
- 为什么测试
test_prop
输出“text_0, text_0”而不是“text_0, text_1”作为测试test_func
?输出表明,test_prop 中的函数 side_effect_func 只被调用一次,而我预计它会被调用两次,就像在 test_func 中一样. - 如何为属性指定 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/