代码之家  ›  专栏  ›  技术社区  ›  Lone Learner

mock上的相同调用链是否总是返回完全相同的mock对象?

  •  0
  • Lone Learner  · 技术社区  · 5 年前

    代码:

    from unittest.mock import Mock
    
    mock = Mock()
    
    print('mock.f():', id(mock.f()))
    print('mock.f().g().h():', id(mock.f().g().h()))
    print('mock():', id(mock()))
    print('mock().f():', id(mock().f()))
    print()
    
    print('mock.f():', id(mock.f()))
    print('mock.f().g().h():', id(mock.f().g().h()))
    print('mock():', id(mock()))
    print('mock().f():', id(mock().f()))
    print()
    
    print('mock.f(1):', id(mock.f(1)))
    print('mock.f(2).g(3).h(4):', id(mock.f(2).g(3).h(4)))
    print('mock(5):', id(mock(5)))
    print('mock(6).f(7):', id(mock(6).f(7)))
    print()
    

    mock.f(): 4483288208
    mock.f().g().h(): 4483354192
    mock(): 4483368976
    mock().f(): 4483708880
    
    mock.f(): 4483288208
    mock.f().g().h(): 4483354192
    mock(): 4483368976
    mock().f(): 4483708880
    
    mock.f(1): 4483288208
    mock.f(2).g(3).h(4): 4483354192
    mock(5): 4483368976
    mock(6).f(7): 4483708880
    

    观察:

    输出显示,对mock的指定链式函数调用总是在程序的生存期内返回相同的对象,而不管我们调用了多少次。

    例如,第一个调用 mock.f().g().h() ,第二个呼叫 模拟.f().g().h() ,甚至第三个调用也有不同的参数 mock.f(2).g(3).h(4)

    问题:

    • 我们能相信这种行为吗?在一个程序的生命周期内, 会返回完全相同的模拟对象吗?
    • 是否保证即使同一个调用链具有不同的参数,例如。, 模拟f(2.g(3.h(4) 模拟.f().g().h() ?

    背景:

    from urllib import request
    from unittest.mock import Mock, patch
    
    with patch('urllib.request.urlopen') as mock_urlopen:
        mock_urlopen.return_value = Mock()
        mock_urlopen().getcode.return_value = 200
        assert request.urlopen('').getcode() == 200
    

    我可以写这样的代码:

    from urllib import request
    from unittest.mock import Mock, patch
    
    with patch('urllib.request.urlopen') as mock_urlopen:
        mock_urlopen().getcode.return_value = 200
        assert request.urlopen('').getcode() == 200
    

    上面的例子太简单了,只是为了演示。我想保留一些独立的例子。但是如果我可以依赖这个特性,当函数调用链很长时,它将变得非常方便。这就是为什么我要寻找某种参考资料或文件,表明我可以依赖这种行为。

    0 回复  |  直到 5 年前
        1
  •  5
  •   Tarun Lalwani    5 年前

    如果你看一下文件 lib/python3.7/unittest/mock.py

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
                raise AttributeError("Mock object has no attribute %r" % name)
        elif _is_magic(name):
            raise AttributeError(name)
        if not self._mock_unsafe:
            if name.startswith(('assert', 'assret')):
                raise AttributeError(name)
    
        result = self._mock_children.get(name)
        if result is _deleted:
            raise AttributeError(name)
        elif result is None:
            wraps = None
            if self._mock_wraps is not None:
                # XXXX should we get the attribute without triggering code
                # execution?
                wraps = getattr(self._mock_wraps, name)
    
            result = self._get_child_mock(
                parent=self, name=name, wraps=wraps, _new_name=name,
                _new_parent=self
            )
            self._mock_children[name]  = result
    
        elif isinstance(result, _SpecState):
            result = create_autospec(
                result.spec, result.spec_set, result.instance,
                result.parent, result.name
            )
            self._mock_children[name]  = result
    
        return result
    

    如您所见,对象被缓存在 _mock_children 因此每个调用都会返回对象。但数据会更新。通过运行下面的代码可以看到

    from unittest.mock import Mock
    
    mock = Mock()
    
    mock.a(10)
    mock.a.assert_called_with(10)
    mock.a(2)
    mock.a.assert_called_with(10)
    

    结果呢

    Traceback (most recent call last):
      File ".../workbench.py", line 8, in <module>
        mock.a.assert_called_with(10)
      File ....lib/python3.7/unittest/mock.py", line 834, in assert_called_with
        raise AssertionError(_error_message()) from cause
    AssertionError: Expected call: a(10)
    Actual call: a(2)