テスト用に python モック フレームワーク (http://www.voidspace.org.uk/python/mock/) を使用しています。スーパークラスをモック アウトし、サブクラスの追加動作のテストに集中したいと考えています。
(興味のある人のために、私はpymongo.collection.Collectionを拡張しました。追加した動作のみをテストしたいのです。テスト目的で別のプロセスとして mongodb を実行する必要はありません。)
この説明では、Aがスーパークラスで、Bがサブクラスです。さらに、以下に示すように、直接および間接のスーパークラス呼び出しを定義します。
class A(object):
def method(self):
...
def another_method(self):
...
class B(A):
def direct_superclass_call(self):
...
A.method(self)
def indirect_superclass_call(self):
...
super(A, self).another_method()
アプローチ #1
MockAというAのモック クラスを定義し、mock.patchを使用して実行時のテストに置き換えます。これは、スーパークラスの直接呼び出しを処理します。次に、B.__bases__ を操作して、間接的なスーパークラス呼び出しを処理します。(下記参照)
発生する問題は、MockAを作成する必要があり、場合によっては ( pymongo.collection.Collectionの場合のように)、モック アウトへのすべての内部呼び出しを解明するために多くの作業が必要になることです。
アプローチ #2
望ましいアプローチは、mock.Mock() クラスを何らかの方法で使用して、モックの呼び出しをジャストインタイムで処理し、テストで return_value または side_effect を定義することです。このように、MockA の定義を避けることで、作業を減らす必要があります。
私が抱えている問題は、 B.__bases__ を変更してmock.Mock()のインスタンスをスーパークラスとして配置できるようにする方法がわからないことです(ここで何らかの直接バインディングを行う必要があります)。これまでのところ、super()は MRO を調べてから、問題のメソッドを定義する最初のクラスを呼び出すと判断しました。スーパークラスにチェックを処理させ、モッククラスに遭遇した場合に成功させる方法がわかりません。この場合、 __getattr__ は使用されていないようです。この時点でメソッドが定義されているとスーパーに考えてもらい、通常どおり mock.Mock() 機能を使用します。
super()は、MRO シーケンスのクラス内で定義されている属性をどのように検出しますか? そして、私がここに割り込んで、どういうわけかその場でmock.Mock()を利用する方法はありますか?
import mock
class A(object):
def __init__(self, value):
self.value = value
def get_value_direct(self):
return self.value
def get_value_indirect(self):
return self.value
class B(A):
def __init__(self, value):
A.__init__(self, value)
def get_value_direct(self):
return A.get_value_direct(self)
def get_value_indirect(self):
return super(B, self).get_value_indirect()
# approach 1 - use a defined MockA
class MockA(object):
def __init__(self, value):
pass
def get_value_direct(self):
return 0
def get_value_indirect(self):
return 0
B.__bases__ = (MockA, ) # - mock superclass
with mock.patch('__main__.A', MockA):
b2 = B(7)
print '\nApproach 1'
print 'expected result = 0'
print 'direct =', b2.get_value_direct()
print 'indirect =', b2.get_value_indirect()
B.__bases__ = (A, ) # - original superclass
# approach 2 - use mock module to mock out superclass
# what does XXX need to be below to use mock.Mock()?
#B.__bases__ = (XXX, )
with mock.patch('__main__.A') as mymock:
b3 = B(7)
mymock.get_value_direct.return_value = 0
mymock.get_value_indirect.return_value = 0
print '\nApproach 2'
print 'expected result = 0'
print 'direct =', b3.get_value_direct()
print 'indirect =', b3.get_value_indirect() # FAILS HERE as the old superclass is called
#B.__bases__ = (A, ) # - original superclass