可以查询Python模拟对象调用的返回值吗?
在Python 3中,mock对象可以用来检查它们被调用时传入的参数。那么,我们能不能也检查它们返回的值呢?
我这里有个具体的例子,我模拟了tempfile.mkdtemp这个函数,但在这个过程中又调用了真实的mkdtemp。我想在我的测试中获取到创建的临时目录。
from unittest import mock
import shutil
import tempfile
from app import production_function
def mkdtemp(*args, **kwargs):
dtemp = orig_mkdtemp(*args, **kwargs)
return dtemp
orig_mkdtemp = tempfile.mkdtemp
patcher = mock.patch('tempfile.mkdtemp', name='tempfile.mkdtemp')
the_mock = patcher.start()
the_mock.side_effect = mkdtemp
# Call function under test
production_function()
assert the_mock.called
# Now, how to get the return value from the call to the_mock?
patcher.stop()
2 个回答
1
如果你的行为是确定性的并且没有状态的话,你可以获取到对你的模拟对象(mock)所做的调用列表,然后再次调用你感兴趣的嵌套调用,以捕获结果。否则,我想你可以这样做:
def mkdtemp_wrapper(result_storage):
def mkdtemp(*args, **kwargs):
dtemp = orig_mkdtemp(*args, **kwargs)
result_storage.append(((*args,**kwargs),dtemp))
return dtemp
return mkdtemp
然后这样修改你的模拟对象:
results_values = []
the_mocks.side_effect = mkdtemp_wrapper(result_values)
这样你在结果值中就会得到一对对的参数和结果的列表。
希望这对你有帮助。
2
很遗憾,mock
模块并不会保存返回值(我用调试工具查看过,确实没有任何痕迹)。你必须在返回side_effect
的值之前先把它存起来。
你可以用一个对象来处理这些“麻烦事”。比如,一个非常简单的实现可以是这样的:
class SideEffect():
def __init__(self, n):
self.values = iter(range(n))
self.return_value = None
def __call__(self):
self.return_value = next(self.values)
return self.return_value
a = Mock()
se = SideEffect(10)
a.side_effect = se
for x in range(10):
v = a()
assert v == se.return_value
print("a()={} return_value={}".format(v, se.return_value))
如果你想要一个更复杂的side_effect
,可以用它来包装一个函数,并处理参数和异常,下面是一个例子:
class GenericSideEffect():
def __init__(self, f, *args, **kwargs):
self.v_function = f
self.args = args
self.kwargs = kwargs
self._return_value = Exception("Never Called")
def __call__(self):
try:
self._return_value = self.v_function(*self.args, **self.kwargs)
return self._return_value
except Exception as e:
self.return_value = e
raise e
@property
def return_value(self):
if isinstance(self._return_value, Exception):
raise self._return_value
return self._return_value
当然,你也可以把它写成装饰器,这样可以保留函数的签名,但我觉得这部分不在这个回答的范围内。