@mock.patch 即使在设置 side_effect 之后也不会引发属性错误

@mock.patch isn't raising an attribute error even after setting side_effect

I'm attempting to fix a bug in the python package caniusepython3 which arises because distlib isn't parsing pypi projects correctly. 我写了这个单元测试

 @mock.patch('distlib.locators.locate')
 def test_blocking_dependencies_locators_fails(self, distlib_mock):
     """
     Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
     """
     py3 = {'py3_project': ''}
     breaking_project = 'test_project'
     distlib_mock.locators.locate.return_value = "foo"
     distlib_mock.locators.locate.side_effect = AttributeError()
     got = dependencies.blocking_dependencies([breaking_project], py3)
     # If you'd like to test that a message is logged we can use 
     # testfixtures.LogCapture or stdout redirects.

So that when distlib fixes the error in the next release of distlib the test case will still be valid.

问题是 MagicMock 从未像我预期的那样引发 AttributeError,而是 returns 魔术模拟对象的字符串表示形式

try:
    # sets dist to <MagicMock name='locate()' id='4447530792'>
    dist = distlib.locators.locate(project)
except AttributeError:
    # This is a work around //bitbucket.org/pypa/distlib/issue/59/
    log.warning('{0} found but had to be skipped.'.format(project))
    continue

稍后会导致此堆栈跟踪,因为它 returns 对象 repr,

======================================================================
ERROR: Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/mock.py", line 1136, in patched
    return func(*args, **keywargs)
  File "/Users/alexlord/git/caniusepython3/caniusepython3/test/test_dependencies.py", line 81, in test_blocking_dependencies_locators_fails
    got = dependencies.blocking_dependencies([breaking_project], py3)
  File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 119, in blocking_dependencies
    return reasons_to_paths(reasons)
  File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 43, in reasons_to_paths
    parent = reasons[blocker]
  File "/Users/alexlord/git/caniusepython3/caniusepython3/dependencies.py", line 29, in __getitem__
    return super(LowerDict, self).__getitem__(key.lower())
nose.proxy.KeyError: <MagicMock name='locate().name.lower().lower()' id='4345929400'>
-------------------- >> begin captured logging << --------------------
ciu: INFO: Checking top-level project: test_project ...
ciu: INFO: Locating <MagicMock name='locate().name.lower()' id='4344734944'>
ciu: INFO: Dependencies of <MagicMock name='locate().name.lower()' id='4344734944'>: []
--------------------- >> end captured logging << ---------------------

为什么调用 distlib.locator.locate() 时 MagicMock 没有返回异常?

更新: 当我切换到使用

时,我能够让这个单元测试工作
def test_blocking_dependencies_locators_fails(self):
    """
    Testing the work around for //bitbucket.org/pypa/distlib/issue/59/
    """
    with mock.patch.object(distlib.locators, 'locate') as locate_mock:
        py3 = {'py3_project': ''}
        breaking_project = 'test_project'
        locate_mock.side_effect = AttributeError()
        got = dependencies.blocking_dependencies([breaking_project], py3)
        # If you'd like to test that a message is logged we can use 
        # testfixtures.LogCapture or stdout redirects.

但我仍然想知道我在装饰器格式上做错了什么。

当您使用 @mock.patch 时,它会模拟您告诉它的内容,并将该模拟对象作为参数传递。因此,您的 distlib_mock 参数 模拟 locate 函数。您实际上是在 distlib.locators.locate.locators.locate 上设置属性。直接在提供的模拟上设置属性,事情应该会更好。

@mock.patch('distlib.locators.locate')
def test_blocking_dependencies_locators_fails(self, locate_mock):
    # ...
    locate_mock.return_value = "foo"
    locate_mock.side_effect = AttributeError()
    # ...