3

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)

h5pyをモックする正しい方法は何h5py.Fileですか?

この問題は、一部のモジュールをモックする必要がある readthedocs を使用したドキュメントでは非常に一般的であるように思えます。コミュニティが答えを持っていることは有益です。

4

1 に答える 1

1

Mock インスタンスをクラスとして機能させることは実際にはできません。Python 2 ではうまく機能せず、Python 3 では偶然にしか機能しません (以下を参照)。

クラス階層で動作させたい場合は、代わりにMock クラス自体を返す必要があります。

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

h5pyをまったくインポートできない場合は、インスタンスではなくクラスを返す、手動で更新されたクラスのリストを保持する必要があることを意味します。

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

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

これは絶対確実ではありません。クラスメソッドで親インスタンスを検出する方法がないためh5py.File().File、実際の実装では別のオブジェクトであったとしても、さらに別の「クラス」が返されます。クラスまたは使用可能な場合はインスタンスにバインドするデコレーターの代わりに使用する新しい記述子を作成することで、部分的に回避できます。そうすれば、少なくともクラスのインスタンスの形でコンテキストを持つことになります。classmethodself._mock_nameMock


Python 3 では、基本クラスとして使用すると、さらにカスタマイズせずにMagicMock 直接使用できます。

>>> from unittest.mock import MagicMock
>>> h5py = MagicMock()
>>> class A(h5py.File): pass
...
>>> class B(A): pass
...

しかし、これは実際には意図的でサポートされている動作ではありません。クラスとサブクラスは、クラス名文字列から「特定」されます。

>>> 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
于 2016-07-18T13:34:17.500 に答える