問題
_mock.patch
_を_autospec=True
_と組み合わせてクラスにパッチを適用しても、そのクラスのインスタンスの属性は保持されません。
詳細
クラスBar
のインスタンスをFoo
と呼ばれるBar
オブジェクト属性としてインスタンス化するクラスfoo
をテストしようとしています。テスト中のBar
メソッドはbar
と呼ばれます。 foo
に属するFoo
インスタンスのメソッドBar
を呼び出します。これをテストするとき、Foo
が正しいBar
メンバーにアクセスしていることをテストしたいだけなので、Foo
をモックしています。
_import unittest
from mock import patch
class Foo(object):
def __init__(self):
self.foo = 'foo'
class Bar(object):
def __init__(self):
self.foo = Foo()
def bar(self):
return self.foo.foo
class TestBar(unittest.TestCase):
@patch('foo.Foo', autospec=True)
def test_patched(self, mock_Foo):
Bar().bar()
def test_unpatched(self):
assert Bar().bar() == 'foo'
_
クラスとメソッドは正常に機能しますが(_test_unpatched
_合格)、_autospec=True
_を使用してテスト(ノーズテストとpytestの両方を使用してテスト)でFooを実行しようとすると、「AttributeError:Mock object has no属性「foo」」
_19:39 $ nosetests -sv foo.py
test_patched (foo.TestBar) ... ERROR
test_unpatched (foo.TestBar) ... ok
======================================================================
ERROR: test_patched (foo.TestBar)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/mock.py", line 1201, in patched
return func(*args, **keywargs)
File "/home/vagrant/dev/constellation/test/foo.py", line 19, in test_patched
Bar().bar()
File "/home/vagrant/dev/constellation/test/foo.py", line 14, in bar
return self.foo.foo
File "/usr/local/lib/python2.7/dist-packages/mock.py", line 658, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'foo'
_
実際、_mock_Foo.return_value.__dict__
_を出力すると、foo
が子またはメソッドのリストにないことがわかります。
_{'_mock_call_args': None,
'_mock_call_args_list': [],
'_mock_call_count': 0,
'_mock_called': False,
'_mock_children': {},
'_mock_delegate': None,
'_mock_methods': ['__class__',
'__delattr__',
'__dict__',
'__doc__',
'__format__',
'__getattribute__',
'__hash__',
'__init__',
'__module__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__'],
'_mock_mock_calls': [],
'_mock_name': '()',
'_mock_new_name': '()',
'_mock_new_parent': <MagicMock name='Foo' spec='Foo' id='38485392'>,
'_mock_parent': <MagicMock name='Foo' spec='Foo' id='38485392'>,
'_mock_wraps': None,
'_spec_class': <class 'foo.Foo'>,
'_spec_set': None,
'method_calls': []}
_
Autospecについての私の理解では、Trueの場合、パッチの仕様は再帰的に適用されるはずです。 fooは確かにFooインスタンスの属性であるため、パッチを適用すべきではありませんか?そうでない場合、Fooインスタンスの属性を保持するためにFooモックを取得するにはどうすればよいですか?
注:
これは、基本的な問題を示す簡単な例です。実際には、サードパーティのモジュールのモックを作成しています。クラス-_consul.Consul
_-そのクライアントは、私が持っているConsulラッパークラスでインスタンス化します。私はconsulモジュールを維持していないので、テストに合わせてソースを変更することはできません(とにかくそれをしたくありません)。価値があるものとして、consul.Consul()
は、属性kv
-_consul.Consul.KV
_のインスタンスを持つconsulクライアントを返します。 kv
にはメソッドget
があり、Consulクラスのインスタンスメソッド_get_key
_にラップしています。 _consul.Consul
_にパッチを適用した後、AttributeError:Mockオブジェクトに属性kvがないため、getの呼び出しが失敗します。
すでにチェックされているリソース:
http://mock.readthedocs.org/en/latest/helpers.html#autospeccinghttp://mock.readthedocs.org/en/latest/patch.html
いいえ、自動スペシングは元のクラスの___init__
_メソッド(または他のメソッド)で設定された属性をモックアウトできません。クラスで見つけることができるすべての静的属性のみをモックアウトできます。
そうしないと、モックは最初にモックで置き換えようとしたクラスのインスタンスを作成する必要がありますが、これは良い考えではありません(インスタンス化時に多くの実リソースを作成するクラスを考えてください)。
自動指定されたモックの再帰的な性質は、これらの静的属性に限定されます。 foo
がクラス属性の場合、Foo().foo
にアクセスすると、その属性の自動指定モックが返されます。 Spam
属性がeggs
型のオブジェクトであるクラスHam
がある場合、_Spam.eggs
_のモックはHam
クラスの自動指定モックになります。
あなたが読んだドキュメントexplicitlyはこれをカバーしています:
より深刻な問題は、インスタンス属性が_
__init__
_メソッドで作成され、クラスにまったく存在しないことが一般的であることです。autospec
は動的に作成された属性を認識できず、APIを表示可能な属性に制限します。
不足している属性を自分でsetするだけです:
_@patch('foo.Foo', autospec=TestFoo)
def test_patched(self, mock_Foo):
mock_Foo.return_value.foo = 'foo'
Bar().bar()
_
または、属性をクラス属性として追加するテスト目的でFoo
クラスのサブクラスを作成します。
_class TestFoo(foo.Foo):
foo = 'foo' # class attribute
@patch('foo.Foo', autospec=TestFoo)
def test_patched(self, mock_Foo):
Bar().bar()
_