9

この質問の鍵は、単体テストを支援することです。忙しい場合__init__(つまり__init__、複雑な初期化を行う場合)、クラスのオブジェクトを単純にインスタンス化することはできませんが、__init__.

この問題を説明するために、次の例を示します。

class SomeClass(object):
    def __init__(self, dep1, dep2, some_string):
        self._dep1 = dep1
        self._dep2 = dep2
        self._some_string = some_string

        # I would need to mock everything here (imagine some even more
        # complicated example)
        for dep2element in self._dep2:
            dep2element.set_dep(dep1)
        self._dep1.set_some_string(some_string)

    def fun1(self):
        ...
    def fun2(self):
        ...
    def fun3(self):
        ...

関数をテストするにはfun*、すべてのテストで複雑な構造を実行する必要があります。

class TestSomeClass(TestCase):
    def create_SomeClass(self, some_string):
        dep1 = Mock()
        # mock everything required by SomeClass' constructor

        dep2 = Mock()
        # mock everything required by SomeClass' constructor

        return SomeClass(dep1, dep2, some_string)

    def test_fun1(self):
        sc = self.create_SomeClass('some string')
        ...

    def test_fun2(self):
        sc = self.create_SomeClass('some other string')
        ...

    def test_fun3(self):
        sc = self.create_SomeClass('yet another string')
        ...

これは冗長だと思います。コンストラクターから作業を移動しない場合でも、Python でこの問題をエレガントに処理する方法を知りたいと思います。

解決:

@ecatmur が示唆したように、特定の機能をテストするには、このコードでうまくいくはずです。

def test_some_method():
    mobject = Mock(SomeClass)
    SomeClass.fun1(mobject)

このアプローチでは、すべてのメソッドがモックアウトされます。fun1実行したい他のメソッドを呼び出す場合(例: fun2)、次のように実行できます。

def test_some_method():
    mobject = Mock(SomeClass)
    mobject.fun2 = SomeClass.fun2.__get__(mobject)
    SomeClass.fun1(mobject)

SomeClass.fun2.__get__(mobject)instancemethod正しいバインディングを提供するものを生成します。

ビバ・エル・パイソン!

元の質問:

元の質問は、行われた作業__init__を別のinit方法に移行することと、そのアプローチを展開するさまざまな問題に集中していました。私の通常のアプローチはこれを作ることです

class SomeClass(object):
    def __init__(self, dep1, dep2, some_string)
        self._dep1 = dep1
        self._dep2 = dep2

        # lots of mumbo-jumbo here...

これになる

class SomeClass(object):
    def __init__(self, dep1, dep2)
        self._dep1 = dep1
        self._dep2 = dep2

    def initiate(self, some-string)
        # lots of mumto-jumbo here...

一般的な感情は、作業を移すこと__init__は一般的な慣行ではなく、経験豊富な Python 開発者にとって意味がないというものでした。

4

5 に答える 5

11

当時とは別に初期化関数を記述した場合__init__、経験豊富な開発者は確かにあなたのコードを子供の遊び場と見なすでしょう。

メソッドを実行せずにクラスのインスタンスのように見えるオブジェクトを作成できるか心配な場合は、Mock__init__を使用します。

def test_some_method():
    mock_object = Mock(MyClass)
    MyClass.some_method(mock_object)
于 2012-09-20T13:21:50.367 に答える
5

__init__実際には何も返せません。それがどのように使用されるかを考えると、これは明らかなはずです。

class Example(object):
    def __init__(self, x):
        self.x = x
        return ANYTHING

e = Example(1)   # how are you going to get ANYTHING back?

initialize()とは別のメソッドを使用するの__init__はちょっとばかげているようです - 全体のポイントは、オブジェクトが作成されたときにイニシャライザを自動的に実行する必要があるため、初期化の例は次のようになります

scobj = SomeClass(dep1, dep2, 'abcdef')
# scobj.initialize('abcdef')     # <= separate call not needed!

編集:

コードを に分解する必要がある場合は、次の__init__ようにプライベート メソッドに入れ、 からそれらを呼び出すことをお勧めします__init__

class Example2(object):
    def __init__(self, a, b, c):
        self._init_connection(a)
        self._init_display(b)
        self.c = c

    def _init_connection(self, a):
        self.conn = make_connection(a)

    def _init_display(self, b):
        self.disp = make_display(b)

...これは良いので

  • オブジェクトの作成時にすべての初期化が行われます (フォローアップの初期化呼び出しを行うことを覚えておく必要はありません)。
  • オブジェクトが初期化されているかどうかを覚えておくためにフラグを保持する必要はありません。フラグが存在する場合は、初期化されています。
于 2012-09-20T13:13:36.277 に答える
2
class Klass:
    def __init__(self):
        initialize instance here, no return

__init__オブジェクトの作成時に自動的に実行されるため、実行されているかどうかを確認する必要はありません。インスタンスがあれば実行されました!

init() から自分自身を返すことについて: インプレース操作であるため、私はしません。これはかなりよく説明されているので、ここで最初の回答を読んでください。

于 2012-09-20T13:05:17.843 に答える
1

どうですか

class SomeClass(object):
    def __init__(self, dep1, dep2, st=None):
        self._dep1 = dep1
        self._dep2 = dep2
        if st is None:
            self._initialized = False
        else:
            self.initialize(st)

    def initialize(self, st):
        ...
        self._initialized = True
        ...

?

于 2012-09-20T13:24:02.820 に答える
1

__init__()別々のメソッドに分割しても問題はありません。これらのメソッドに適切な名前を付けて再利用可能にする方法でそれを行うことができれば、なおさらです - 例:

class SomeClass(object):
    def __init__(self, dep1, dep2, some_string):
        self.set_dependencies(dep1, dep2, some_string)

    def set_dependencies(self, dep1, dep2, some_string):
        self._dep1 = dep1
        self._dep2 = dep2
        self._some_string = some_string

        for dep2element in self._dep2:
            dep2element.set_dep(dep1)
        self._dep1.set_some_string(some_string)

ここで、remove_dependencies()新しい依存関係を設定する前に、インスタンスをニュートラルな状態に戻すメソッドを将来追加することができます。この機能は、初期化プロセスから分離されています。

__init__()これをテストで実行したくない場合でも、これを から呼び出したことに注意してください。これをから呼び出さない__init__()場合、このクラスを使用する誰かがset_dependencies()インスタンス化後に呼び出す必要があります。これは、クラスの API にとって不必要な複雑さです。

できることは、テスト用にメソッドをスタブ化することです。たとえば、次のようになります。

class TestSomeClass(TestCase):
    def __init__(self):
        SomeClass._old_set_dependencies = SomeClass.set_dependencies
        SomeClass.set_dependencies = lambda *args: None

    ...

これは、ポイントを実際に証明するためだけに、メソッドをスタブ化するかなり大胆な方法です-より熱心に行うための優れたライブラリがいくつかあります-この議論を参照してください。ただし、 Mockの拡張であるMockstarもお勧めしますセマンティクスを単純化します。

于 2012-09-23T23:59:02.823 に答える