可以查询Python模拟对象调用的返回值吗?

3 投票
2 回答
2003 浏览
提问于 2025-04-30 20:21

在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

当然,你也可以把它写成装饰器,这样可以保留函数的签名,但我觉得这部分不在这个回答的范围内。

撰写回答