19

テスト用に 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
4

2 に答える 2

6

私がここに割り込んで、どういうわけかその場でmock.Mock()を利用する方法はありますか?

より良いアプローチがあるかもしれませんが、いつでも独自の方法を作成super()して、モックしているクラスを含むモジュールに挿入できます。呼び出し元に基づいて、必要なものを返すようにします。

現在の名前空間で定義するsuper()だけ (この場合、再定義は定義後の現在のモジュールにのみ適用されます)、またはimport __builtin__再定義を に適用することができます__builtin__.super。この場合、再定義は Python セッションでグローバルに適用されます。

デフォルトの引数を使用して、元の関数をキャプチャできsuperます (実装から呼び出す必要がある場合)。

def super(type, obj=None, super=super):  
    # inside the function, super refers to the built-in
于 2012-06-25T20:15:06.800 に答える
3

kindall の提案に従って、super() のモックをいじってみました。残念ながら、多大な努力の結果、複雑な継承のケースを処理するのは非常に複雑になりました。

いくつかの作業の後、MRO を介して属性を解決するときに、super() がクラスの __dict__ に直接アクセスすることに気付きました (getattr タイプの呼び出しは行いません)。解決策は、mock.MagicMock() オブジェクトを拡張し、クラスでラップしてこれを実現することです。ラップされたクラスは、サブクラスの __bases__ 変数に配置できます。

ラップされたオブジェクトは、ターゲット クラスのすべての定義済み属性をラップ クラスの __dict__ に反映するため、super() 呼び出しは、内部 MagicMock() 内の適切にパッチされた属性に解決されます。

次のコードは、これまでのところ機能することがわかったソリューションです。これを実際にはコンテキスト ハンドラ内に実装していることに注意してください。また、他のモジュールからインポートする場合は、適切な名前空間にパッチを適用するように注意する必要があります。

これは、アプローチを示す簡単な例です。

from mock import MagicMock
import inspect 


class _WrappedMagicMock(MagicMock):
    def __init__(self, *args, **kwds):
        object.__setattr__(self, '_mockclass_wrapper', None)
        super(_WrappedMagicMock, self).__init__(*args, **kwds)

    def wrap(self, cls):
        # get defined attribtues of spec class that need to be preset
        base_attrs = dir(type('Dummy', (object,), {}))
        attrs = inspect.getmembers(self._spec_class)
        new_attrs = [a[0] for a in attrs if a[0] not in base_attrs]

        # pre set mocks for attributes in the target mock class
        for name in new_attrs:
            setattr(cls, name, getattr(self, name))

        # eat up any attempts to initialize the target mock class
        setattr(cls, '__init__', lambda *args, **kwds: None)

        object.__setattr__(self, '_mockclass_wrapper', cls)

    def unwrap(self):
        object.__setattr__(self, '_mockclass_wrapper', None)

    def __setattr__(self, name, value):
        super(_WrappedMagicMock, self).__setattr__(name, value)

        # be sure to reflect to changes wrapper class if activated
        if self._mockclass_wrapper is not None:
            setattr(self._mockclass_wrapper, name, value)

    def _get_child_mock(self, **kwds):
        # when created children mocks need only be MagicMocks
        return MagicMock(**kwds)


class A(object):
    x = 1

    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):
        super(B, self).__init__(value)

    def f(self):
        return 2

    def get_value_direct(self):
        return A.get_value_direct(self)

    def get_value_indirect(self):
        return super(B, self).get_value_indirect()

# nominal behavior
b = B(3)
assert b.get_value_direct() == 3
assert b.get_value_indirect() == 3
assert b.f() == 2
assert b.x == 1

# using mock class
MockClass = type('MockClassWrapper', (), {})
mock = _WrappedMagicMock(A)
mock.wrap(MockClass)

# patch the mock in
B.__bases__ = (MockClass, )
A = MockClass

# set values within the mock
mock.x = 0
mock.get_value_direct.return_value = 0
mock.get_value_indirect.return_value = 0

# mocked behavior
b = B(7)
assert b.get_value_direct() == 0
assert b.get_value_indirect() == 0
assert b.f() == 2
assert b.x == 0
于 2012-12-07T20:52:30.743 に答える