15

DB接続メソッドと依存関係の混乱を伴う、非常に複雑な別のクラスから継承するクラスをテストしています。サブクラスで定義されたメソッドをうまく操作できるように、その基本クラスをモックしたいのですが、モック化されたクラスから継承した瞬間に、オブジェクト自体がモックになり、そのすべてのメソッドが失われます。

スーパークラスをモックするにはどうすればよいですか?

多かれ少なかれ状況は次のように要約できます。

import mock

ClassMock = mock.MagicMock()

class RealClass(ClassMock):

    def lol(self):
        print 'lol'

real = RealClass()
real.lol()  # Does not print lol, but returns another mock

print real # prints <MagicMock id='...'>

これは単純化されたケースです。実際に起こっているのはRealClassextendsAnotherClassですが、何とかインターセプトAnotherClassしてモックに置き換えることができました。

4

2 に答える 2

19

これは私が長い間苦労してきたことですが、ようやく解決策を見つけたと思います。

既にお気付きのように、基本クラスをモックに置き換えようとすると、テストしようとしているクラスが単にモックになり、それをテストする能力が失われます。解決策は、基本クラス全体ではなく、基本クラスのメソッドのみをモックすることですが、これは言うは易く行うは難しです。テストごとにすべてのメソッドを 1 つずつモックすると、エラーが発生しやすくなる可能性があります。

私が代わりに行ったのは、別のクラスをスキャンするクラスを作成し、それ自体Mock()に他のクラスのメソッドに一致する s を割り当てることです。その後、テストで実際の基本クラスの代わりにこのクラスを使用できます。

偽のクラスは次のとおりです。

class Fake(object):
    """Create Mock()ed methods that match another class's methods."""

    @classmethod
    def imitate(cls, *others):
        for other in others:
            for name in other.__dict__:
                try:
                    setattr(cls, name, Mock())
                except (TypeError, AttributeError):
                    pass
        return cls

したがって、たとえば、次のようなコードがあるかもしれません (これは少し不自然で申し訳ありBaseClassません。SecondClassと は重要な作業を行っており、多くのメソッドが含まれており、必ずしもあなたによって定義されているとは限りません):

class BaseClass:
    def do_expensive_calculation(self):
        return 5 + 5

class SecondClass:
    def do_second_calculation(self):
        return 2 * 2

class MyClass(BaseClass, SecondClass):
    def my_calculation(self):
        return self.do_expensive_calculation(), self.do_second_calculation()

次に、次のようないくつかのテストを作成できます。

class MyTestCase(unittest.TestCase):
    def setUp(self):
        MyClass.__bases__ = (Fake.imitate(BaseClass, SecondBase),)

    def test_my_methods_only(self):
        myclass = MyClass()
        self.assertEqual(myclass.my_calculation(), (
            myclass.do_expensive_calculation.return_value, 
            myclass.do_second_calculation.return_value,
        ))
        myclass.do_expensive_calculation.assert_called_once_with()
        myclass.do_second_calculation.assert_called_once_with()

したがって、基本クラスに存在するメソッドは、対話可能なモックとして引き続き使用できますが、クラス自体はモックにはなりません。

そして、これが python2 と python3 の両方で機能するように注意しました。

于 2015-02-23T02:30:40.750 に答える