Mock modules and subclasses (TypeError: Error when calling the metaclass bases)

Mock modules and subclasses (TypeError: Error when calling the metaclass bases)

要在 readthedocs 上编译文档,必须模拟模块 h5py。我得到一个可以用这个简单的代码重现的错误:

from __future__ import print_function
import sys

try:
    from unittest.mock import MagicMock
except ImportError:
    # Python 2
    from mock import Mock as MagicMock


class Mock(MagicMock):
    @classmethod
    def __getattr__(cls, name):
        return Mock()

sys.modules.update({'h5py': Mock()})

import h5py
print(h5py.File, type(h5py.File))


class A(h5py.File):
    pass

print(A, type(A))


class B(A):
    pass

这个脚本的输出是:

<Mock id='140342061004112'> <class 'mock.mock.Mock'>
<Mock spec='str' id='140342061005584'> <class 'mock.mock.Mock'>
Traceback (most recent call last):
  File "problem_mock.py", line 32, in <module>
class B(A):
TypeError: Error when calling the metaclass bases
    str() takes at most 1 argument (3 given)

模拟 h5pyh5py.File 的正确方法是什么?

在我看来,这个问题对于带有 readthedocs 的文档来说非常普遍,其中某些模块必须被模拟。社区得到答案会很有用。

你不能真正使用Mock实例来充当classes;它在 Python 2 上严重失败,并且在 Python 3 上工作只是偶然(见下文)。

如果你想让它们在 [=56= 中工作,你必须 return Mock class ]层次结构:

>>> class A(Mock):  # note, not called!
...     pass
...
>>> class B(A):
...     pass
...
>>> B
<class '__main__.B'>
>>> B()
<B id='4394742480'>

如果您根本无法导入 h5py ,这意味着您需要手动更新 classes 列表,其中 return class 而不是实例:

_classnames = {
    'File',
    # ...
}

class Mock(MagicMock):
    @classmethod
    def __getattr__(cls, name):
        return Mock if name in _classnames else Mock()

这不是万无一失的;无法在 class 方法中检测父实例,因此 h5py.File().File 会导致另一个 'class' 被 returned 即使在实际实现中是一些其他对象代替。您可以 部分 通过创建一个新的描述符来解决这个问题,而不是 classmethod 装饰器,该装饰器将绑定到 class 给一个实例(如果有的话);这样你至少会在 Mock class.

的实例上有一个 self._mock_name 形式的上下文

在 Python 3 中,直接使用 MagicMock 作为基础 class:

无需进一步自定义即可
>>> from unittest.mock import MagicMock
>>> h5py = MagicMock()
>>> class A(h5py.File): pass
...
>>> class B(A): pass
...

但这并不是真正有意和受支持的行为; classes 和 subclasses 是 'specced' 来自 classname string:

>>> A
<MagicMock spec='str' id='4353980960'>
>>> B
<MagicMock spec='str' id='4354132344'>

因此在实例化不起作用时会出现各种问题:

>>> A()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/unittest/mock.py", line 917, in __call__
    return _mock_self._mock_call(*args, **kwargs)
  File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python3.5/unittest/mock.py", line 976, in _mock_call
    result = next(effect)
StopIteration