web-dev-qa-db-ja.com

シングルトンクラスのモック

私は最近、クラスのシングルトンを作成するとクラスのオブジェクトをモックすることができなくなり、クライアントをテストすることが難しくなることを読みました。根本的な理由をすぐに理解できませんでした。誰かがシングルトンクラスをm笑することを不可能にする理由を説明できますか?また、クラスシングルトンの作成に関連する問題はありますか?

52
Aadith

定義上、シングルトンにはインスタンスが1つだけあります。したがって、その作成はクラス自体によって厳密に制御されます。通常、これはインターフェイスではなく具象クラスであり、プライベートコンストラクターのためサブクラス化できません。さらに、(Singleton.getInstance()または同等のものを呼び出すことにより)クライアントによってアクティブに検出されるため、たとえば Dependency Injection その「実際の」インスタンスを模擬インスタンスに置き換えるには:

class Singleton {
    private static final myInstance = new Singleton();
    public static Singleton getInstance () { return myInstance; }
    private Singleton() { ... }
    // public methods
}

class Client {
    public doSomething() {
        Singleton singleton = Singleton.getInstance();
        // use the singleton
    }
}

モックの場合、理想的には、自由にサブクラス化でき、その具体的な実装が依存性注入によってクライアントに提供されるインターフェースが必要です。

シングルトンの実装を緩和して、テスト可能にすることができます

  • モックサブクラスと「実際の」サブクラスで実装できるインターフェイスを提供する
  • setInstanceメソッドを追加して、単体テストでインスタンスを置換できるようにします

例:

interface Singleton {
    private static final myInstance;
    public static Singleton getInstance() { return myInstance; }
    public static void setInstance(Singleton newInstance) { myInstance = newInstance; }
    // public method declarations
}

// Used in production
class RealSingleton implements Singleton {
    // public methods
}

// Used in unit tests
class FakeSingleton implements Singleton {
    // public methods
}

class ClientTest {
    private Singleton testSingleton = new FakeSingleton();
    @Test
    public void test() {
        Singleton.setSingleton(testSingleton);
        client.doSomething();
        // ...
    }
}

ご覧のとおり、Singletonの「クリーンさ」を損なうことによってのみ、Singletonを使用するコードユニットをテスト可能にすることができます。最終的には、回避できる場合はまったく使用しないことをお勧めします。

更新:そして、これは、Michael Feathersによる レガシーコードを効果的に使用する への必須の参照です。

13
Péter Török

シングルトンの実装に大きく依存します。しかし、主にプライベートコンストラクターがあるため、拡張することはできません。ただし、次のオプションがあります

  • インターフェイスを作成する-SingletonInterface
  • シングルトンクラスにそのインターフェイスを実装させる
  • let Singleton.getInstance() return SingletonInterface
  • テストでSingletonInterfaceの模擬実装を提供する
  • private staticリフレクションを使用するSingletonのフィールド。

ただし、シングルトン(グローバルな状態を表す)は避けたほうがいいでしょう。 この講義 テスト可能性の観点からいくつかの重要な設計概念を説明します。

9
Bozho

シングルトンパターン自体が純粋な悪であるというわけではありませんが、それが不適切な状況であっても、それは非常に使い古されています。多くの開発者は、「ああ、たぶんこれらのうちの1つだけが必要になるので、それをシングルトンにしましょう」と考えています。実際、「これらのうちの1つだけが必要になる可能性があるので、プログラムの開始時に作成し、必要な場所に参照を渡しましょう」と考える必要があります。

シングルトンとテストの最初の問題は、シングルトンのためではなく、怠のためです。シングルトンを取得するのが便利なため、シングルトンオブジェクトへの依存関係は多くの場合メソッドに直接埋め込まれます。これにより、シングルトンを同じインターフェイスで異なる実装(モックオブジェクトなど)を持つ別のオブジェクトに変更することが非常に難しくなります。 )。

の代わりに:

void foo() {
   Bar bar = Bar.getInstance();
   // etc...
}

好む:

void foo(IBar bar) {
   // etc...
}

これで、制御可能なモックされたfooオブジェクトを使用して、関数barをテストできます。 fooをテストせずにbarをテストできるように、依存関係を削除しました。

シングルトンとテストに関する他の問題は、シングルトン自体をテストする場合です。シングルトンは(設計上)再構築するのが非常に難しいため、たとえば、シングルトンコンストラクターをテストできるのは1回だけです。 Barの単一のインスタンスがテスト間で状態を保持し、テストの実行順序に応じて成功または失敗する可能性もあります。

3
Mark Byers

シングルトンをモックする方法があります。 powermockを使用して静的メソッドをモックし、Whiteboxを使用してコンストラクターを呼び出しますYourClass mockHelper = Whitebox .invokeConstructor(YourClass.class); Whitebox.setInternalState(mockHelper, "yourdata",mockedData); PowerMockito.mockStatic(YourClass.class); Mockito.when(YourClass.getInstance()).thenReturn(mockHelper);

何が起きているかは、シングルトンのバイトコードが実行時に変化していることです。

楽しい

1
Moshe Tsabari