web-dev-qa-db-ja.com

テスト対象のユニット:ImplまたはInterface?

それを実装するインターフェースと実装クラスがあり、これのための単体テストを作成したいとします。インターフェイスまたはImplは何をテストする必要がありますか?

次に例を示します。

_public interface HelloInterface {
    public void sayHello();
}


public class HelloInterfaceImpl implements HelloInterface {
    private PrintStream target = System.out;


    @Override
    public void sayHello() {
        target.print("Hello World");

    }

    public void setTarget(PrintStream target){
        this.target = target;
    }
}
_

それで、私はそれを実装するHelloInterfaceとHelloInterfaceImplを持っています。テスト対象のユニットまたはImplとは何ですか?

HelloInterfaceだと思います。 JUnitテストの次のスケッチを検討してください。

_public class HelloInterfaceTest {
    private HelloInterface hi;

    @Before
    public void setUp() {
        hi = new HelloInterfaceImpl();
    }

    @Test
    public void testDefaultBehaviourEndsNormally() {
        hi.sayHello();
        // no NullPointerException here
    }

    @Test
    public void testCheckHelloWorld() throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintStream target = new PrintStream(out);
        PrivilegedAccessor.setValue(hi, "target", target);
        //You can use ReflectionTestUtils in place of PrivilegedAccessor
        //really it is DI 
        //((HelloInterfaceImpl)hi).setTarget(target);
        hi.sayHello();
        String result = out.toString();
        assertEquals("Hello World", result);

    }
 }
_

メインラインは実際にコメントアウトしたものです。

_((HelloInterfaceImpl)hi).setTarget(target);_

メソッドsetTarget()は私のパブリックインターフェイスの一部ではないため、誤って呼び出したくありません。本当に電話したいのなら、少し時間をかけて考えてみるべきです。たとえば、私が本当にやろうとしているのは依存関係の注入であることを発見するのに役立ちます。それは私に新しい機会の全世界を開きます。既存の依存性注入メカニズム(たとえば、Spring)を使用できます。実際にコードで行ったように自分でシミュレートしたり、まったく異なるアプローチを取ることができます。よく見ると、PrintSreamの準備はそれほど簡単ではありませんでした。代わりにモックオブジェクトを使用する必要がありますか?

[〜#〜] edit [〜#〜]常にに焦点を当てるべきだと思いますインターフェース。私の観点からは、setTarget()はimplクラスの「契約」の一部でもありません。依存性注入のサリーとして機能します。 Implクラスのパブリックメソッドは、テストの観点からプライベートと見なす必要があると思います。ただし、実装の詳細を無視しているわけではありません。

参照 Private/Protectedメソッドは単体テストの下にあるべきですか?

EDIT-2複数の実装\複数のインターフェイスの場合、すべての実装をテストしますが、setUp()メソッド私は間違いなくインターフェースを使用します。

31
alexsmail

実装は、テストする必要があるユニットです。それはもちろん、インスタンス化しているものであり、プログラム/ビジネスロジックを含んでいます。

重要なインターフェースがあり、すべての実装が適切に準拠していることを確認したい場合は、インターフェースに焦点を当て、インスタンスを渡す必要があるテストスイートを作成できます(実装タイプに依存しません)。

はい、おそらくPrintStreamにMockitoを使用する方が簡単です。この特定の例で行ったように、モックオブジェクトの使用を回避することが常に可能であるとは限りません。

17
Tim Bender

インターフェイスにテストします。

System.outに書き込むのが難しいように実装を記述していたことが間違いだったと思います。別のPrintStreamでオーバーライドする方法はありませんでした。私はセッターの代わりにコンストラクターを使用したでしょう。そのように模擬したりキャストしたりする必要はありません。

これは単純なケースです。より複雑なものには、異なる、より複雑なインターフェースの実装を作成するためのファクトリーがあると思います。うまくいけば、あなたが箱に入れられるような方法でそれを設計しないでしょう。

テストでインターフェイスに固執することで、モックの作成も非常に簡単になります。

public class HelloInterfaceImpl implements HelloInterface {

    private PrintStream target;

    public HelloInterfaceImpl() {
        this(System.out);
    }

    public HelloInterfaceImpl(PrintStream ps) { 
       this.target = ps;
    }

    @Override
    public void sayHello() {
        target.print("Hello World");
    }
}

ここにテストがあります:

public class HelloInterfaceTest {

    @Test
    public void testDefaultBehaviourEndsNormally() {
        HelloInterface hi = new HelloInterfaceImpl();    
        hi.sayHello();
    }

    @Test
    public void testCheckHelloWorld() throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintStream target = new PrintStream(out);
        HelloInterface hi = new HelloInterfaceImpl(target);    
        hi.sayHello();
        String result = out.toString();
        assertEquals("Hello World", result);
    }
}
7
duffymo

私は常に実装をテストしています。1つのクラスが複数のインターフェースを実装でき、1つのインターフェースを複数のクラスで実装できます。それぞれをテストでカバーする必要があります。

単体テストでセッターを呼び出す要件(実装へのインターフェースのキャスト):

((HelloInterfaceImpl)hi).setTarget(target);

実際に実装をテストすることを意味します。これはコントラクトの一部ではありませんが、実装を機能させるための重要な部分であり、適切にテストする必要があります。

JDKの例を見てみましょう。インターフェースListと2つの実装ArrayListLinkedListがあります。さらに、LinkedListDequeインターフェースを実装します。 Listインターフェースのテストを作成する場合、何をカバーしますか?配列またはリンクリスト? LinkedListの場合、さらに何をテストしますか? DequeまたはList?ご覧のとおり、実装をテストすると、そのような問題は発生しません。

私個人的には、単体テストでインターフェースを実装にキャストすることは、何かがうまくいっていないことの明らかな兆候です;)

6
omnomnom

それは実装に依存し、インターフェイスのコントラクトを超えて何をするかによって異なります。多くの実装は、インターフェースで提供される機能のみを実装します。他の実装では、インターフェースはクラス機能のごく一部にすぎません。複数のインターフェースを実装する場合があります。

最終的に、実装をテストします。

あなたが定義したような単純なケースでは、私は6のうちの6つを他の6つと言います。 implementationを十分にテストする限り、テストケースをインターフェイスまたは実装に書き込み、結果は同じです。

realリーダーとライターを装飾することにより、通信チャネルの統計を収集するクラスがある別の例を見てみましょう。私のクラスはこれらのインターフェースを実装するかもしれませんが、どちらのコントラクトとも関係のない統計も収集します。もちろん、これらのインターフェイスに基づくテストを作成することはできますが、このクラスを完全にはテストしません。

1
Robin