40
class HelloWorld(object):
    def say_it(self):
        return 'Hello I am Hello World'

def i_call_hello_world(hw_obj):
    print 'here... check type: %s' %type(HelloWorld)
    if isinstance(hw_obj, HelloWorld):
        print hw_obj.say_it()

from mock import patch, MagicMock
import unittest

class TestInstance(unittest.TestCase):
    @patch('__main__.HelloWorld', spec=HelloWorld)
    def test_mock(self,MK):
        print type(MK)
        MK.say_it.return_value = 'I am fake'
        v = i_call_hello_world(MK)
        print v

if __name__ == '__main__':
    c = HelloWorld()
    i_call_hello_world(c)
    print isinstance(c, HelloWorld)
    unittest.main()

トレースバックはこちら

here... check type: <type 'type'>
Hello I am Hello World
True
<class 'mock.MagicMock'>
here... check type: <class 'mock.MagicMock'>
E
======================================================================
ERROR: test_mock (__main__.TestInstance)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/mock.py", line 1224, in patched
    return func(*args, **keywargs)
  File "t.py", line 18, in test_mock
    v = i_call_hello_world(MK)
  File "t.py", line 7, in i_call_hello_world
    if isinstance(hw_obj, HelloWorld):
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types

----------------------------------------------------------------------
Ran 1 test in 0.002s

Q1. このエラーがスローされるのはなぜですか? 彼らです<class type='MagicMock>

Q2. エラーが修正された場合に最初の行が通過するように、モックを一時停止するにはどうすればよいですか?

ドキュメントから:

通常、__class__オブジェクトの属性はその型を返します。仕様を持つモック オブジェクトの場合、__class__代わりに仕様クラスを返します。これにより、モック オブジェクトisinstance()は、次のように置き換え/マスカレードしているオブジェクトのテストに合格できます。

mock = Mock(spec=3)
isinstance(mock, int)
True
4

9 に答える 9

64

IMHOこれは良い質問であり、「を使用しないisinstanceで、代わりにダックタイピングを使用してください」と言うのは悪い答えです。ダックタイピングは素晴らしいですが、特効薬ではありません。isinstancePythonic でなくても、必要な場合があります。たとえば、pythonic ではないライブラリまたはレガシー コードを使用する場合は、isinstance. それはまさに現実世界であり、モックはこの種の作業に合うように設計されています。

コードで大きな間違いを犯すのは、次のように書くときです。

@patch('__main__.HelloWorld', spec=HelloWorld)
def test_mock(self,MK):

patch私たちが読んだドキュメントから(強調は私のものです):

関数の本体または with ステートメント内で、ターゲットに新しいオブジェクトがパッチされます。

つまり、HelloWorld クラス オブジェクトにパッチを適用すると、参照が関数のコンテキストのオブジェクトにHelloWorld置き換えられます。MagicMocktest_mock()

次に、 wheni_call_hello_world()が実行されるのif isinstance(hw_obj, HelloWorld): HelloWorldMagicMock()オブジェクトであり、クラスではありません(エラーが示唆するように)。

その動作は、クラス参照にパッチを適用することの副作用として、 の 2 番目の引数がisinstance(hw_obj, HelloWorld)オブジェクト (MagicMockインスタンス) になるためです。これはclassまたは ではありませんtype。この動作を理解するための簡単な実験はi_call_hello_world()、次のように変更することです。

HelloWorld_cache = HelloWorld

def i_call_hello_world(hw_obj):
    print 'here... check type: %s' %type(HelloWorld_cache)
    if isinstance(hw_obj, HelloWorld_cache):
        print hw_obj.say_it()

モジュールをロードすると、HelloWorldクラスへの元の参照が保存されるため、エラーは消えます。HelloWorld_cacheパッチが適用されると、変更されるだけHelloWorldで変更されませんHelloWorld_cache

残念ながら、前の実験では、ライブラリやレガシー コードを変更してこのようなトリックを導入することはできないため、あなたのようなケースで遊ぶ方法はありません。さらに、これらは私たちのコードでは絶対に見たくない種類のトリックです。

良いニュースは、何かを行うことができるということですが、テストするコードがあるモジュール内 patchの参照だけではできません。最善の方法は、解決しなければならない実際のケースによって異なります。あなたの例では、オブジェクトとして使用する を作成し、引数を使用してそれをインスタンスとしてドレスアップし、テストに合格することができます。これはまさに設計の目的の 1 つです。テストは次のように記述されます。HelloWordisinstace(o,HelloWord)MockHelloWorldspecHelloWorldisinstancespec

def test_mock(self):
    MK = MagicMock(spec=HelloWorld) #The hw_obj passed to i_call_hello_world
    print type(MK)
    MK.say_it.return_value = 'I am fake'
    v = i_call_hello_world(MK)
    print v

そして、ユニットテスト部分だけの出力は

<class 'mock.MagicMock'>
here... check type: <type 'type'>
I am fake
None
于 2014-10-25T22:08:20.813 に答える
12

Michele d'Amico は私の見解では正しい答えを提供しており、それを読むことを強くお勧めします。しかし、それにはしばらく時間がかかりました。将来この質問に戻ってくると確信しているので、最小限のコード例が解決策を明確にし、クイックリファレンスを提供するのに役立つと思いました:

from mock import patch, mock

class Foo(object): pass

# Cache the Foo class so it will be available for isinstance assert.
FooCache = Foo

with patch('__main__.Foo', spec=Foo):
    foo = Foo()
    assert isinstance(foo, FooCache)
    assert isinstance(foo, mock.mock.NonCallableMagicMock)

    # This will cause error from question:
    # TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
    assert isinstance(foo, Foo)
于 2016-01-06T22:20:21.050 に答える
1

isinstance メソッドに次のパッチを適用するだけです。

@patch('__main__.isinstance', return_value=True)

したがって、期待される動作とカバレッジが得られます。モック メソッドが呼び出されたことをいつでもアサートできます。以下のテスト ケース サンプルを参照してください。

class HelloWorld(object):
    def say_it(self):
        return 'Hello I am Hello World'

def i_call_hello_world(hw_obj):
    print('here... check type: %s' %type(HelloWorld))
    if isinstance(hw_obj, HelloWorld):
        print(hw_obj.say_it())

from unittest.mock import patch, MagicMock
import unittest

class TestInstance(unittest.TestCase):
    @patch('__main__.isinstance', return_value=True)
    def test_mock(self,MK):
        print(type(MK))
        MK.say_it.return_value = 'I am fake'
        v = i_call_hello_world(MK)
        print(v)
        self.assertTrue(MK.say_it.called)

    @patch('__main__.isinstance', return_value=False)
    def test_not_call(self, MK):
        print(type(MK))
        MK.say_it.return_value = 'I am fake'
        v = i_call_hello_world(MK)
        print(v)
        self.assertFalse(MK.say_it.called)
于 2020-10-07T11:36:52.207 に答える
0

考えられる解決策は、オブジェクトのサブクラスをチェックすることだと思います。

issubclass(hw_obj.__class__, HelloWorld)

例:

from unittest.mock import patch, MagicMock
import unittest


class HelloWorld(object):
    def say_it(self):
        return 'Hello I am Hello World'


def i_call_hello_world(hw_obj):
    print('here... check type: %s' % type(HelloWorld))
    if isinstance(hw_obj, HelloWorld) or issubclass(hw_obj.__class__, HelloWorld):
        print(hw_obj.say_it())


class TestInstance(unittest.TestCase):
    @patch('__main__.isinstance', return_value=True)
    def test_mock(self, MK):
        print(type(MK))
        MK.say_it.return_value = 'I am fake'
        v = i_call_hello_world(MK)
        print(v)
        self.assertTrue(MK.say_it.called)

    @patch('__main__.isinstance', return_value=False)
    def test_not_call(self, MK):
        print(type(MK))
        MK.say_it.return_value = 'I am fake'
        v = i_call_hello_world(MK)
        print(v)
        self.assertFalse(MK.say_it.called)


if __name__ == '__main__':
    unittest.main()
于 2021-12-29T18:22:52.650 に答える
-6

を使用しないでくださいisinstance。代わりに、say_itメソッドの存在を確認してください。メソッドが存在する場合は、次のように呼び出します。

if hasattr(hw_obj, 'say_it'):
    print hw_obj.say_it()

とにかく、これはより良い設計です。型情報に依存することは、はるかに脆弱です。

于 2012-06-21T21:09:55.847 に答える