web-dev-qa-db-ja.com

インターフェースのjunitテストを書く方法は?

インターフェースを具体的な実装クラスに使用できるように、インターフェースのjunitテストを記述する最良の方法は何ですか?

例えばこのインターフェースと実装クラスがあります:

public interface MyInterface {
    /** Return the given value. */
    public boolean myMethod(boolean retVal);
}

public class MyClass1 implements MyInterface {
    public boolean myMethod(boolean retVal) {
        return retVal;
    }
}

public class MyClass2 implements MyInterface {
    public boolean myMethod(boolean retVal) {
        return retVal;
    }
}

クラスに対して使用できるように、インターフェイスに対するテストをどのように作成しますか?

可能性1:

public abstract class MyInterfaceTest {
    public abstract MyInterface createInstance();

    @Test
    public final void testMyMethod_True() {
        MyInterface instance = createInstance();
        assertTrue(instance.myMethod(true));
    }

    @Test
    public final void testMyMethod_False() {
        MyInterface instance = createInstance();
        assertFalse(instance.myMethod(false));
    }
}

public class MyClass1Test extends MyInterfaceTest {
    public MyInterface createInstance() {
        return new MyClass1();
    }
}

public class MyClass2Test extends MyInterfaceTest {
    public MyInterface createInstance() {
        return new MyClass2();
    }
}

プロ:

  • 実装するメソッドは1つだけです

短所:

  • テスト対象クラスの依存関係とモックオブジェクトは、すべてのテストで同じである必要があります

可能性2:

public abstract class MyInterfaceTest
    public void testMyMethod_True(MyInterface instance) {
        assertTrue(instance.myMethod(true));
    }

    public void testMyMethod_False(MyInterface instance) {
        assertFalse(instance.myMethod(false));
    }
}

public class MyClass1Test extends MyInterfaceTest {
    @Test
    public void testMyMethod_True() {
        MyClass1 instance = new MyClass1();
        super.testMyMethod_True(instance);
    }

    @Test
    public void testMyMethod_False() {
        MyClass1 instance = new MyClass1();
        super.testMyMethod_False(instance);
    }
}

public class MyClass2Test extends MyInterfaceTest {
    @Test
    public void testMyMethod_True() {
        MyClass1 instance = new MyClass2();
        super.testMyMethod_True(instance);
    }

    @Test
    public void testMyMethod_False() {
        MyClass1 instance = new MyClass2();
        super.testMyMethod_False(instance);
    }
}

プロ:

  • 依存関係およびモックオブジェクトを含む各テストの詳細なグラニュレーション

短所:

  • 実装するテストクラスごとに、追加のテストメソッドを記述する必要があります

あなたはどの可能性を好むでしょうか、または他のどのような方法を使用しますか?

68
Xeno Lupus

@dlevが大いに投票した回答とは反対に、あなたが提案しているようなテストを書くことは非常に有用/必要な場合があります。インターフェイスを通じて表現されるクラスのパブリックAPIは、テストする最も重要なものです。そうは言っても、どちらのアプローチも使用しませんが、代わりに Parameterized テストを使用します。ここで、パラメーターはテストする実装です。

@RunWith(Parameterized.class)
public class InterfaceTesting {
    public MyInterface myInterface;

    public InterfaceTesting(MyInterface myInterface) {
        this.myInterface = myInterface;
    }

    @Test
    public final void testMyMethod_True() {
        assertTrue(myInterface.myMethod(true));
    }

    @Test
    public final void testMyMethod_False() {
        assertFalse(myInterface.myMethod(false));
    }

    @Parameterized.Parameters
    public static Collection<Object[]> instancesToTest() {
        return Arrays.asList(
                    new Object[]{new MyClass1()},
                    new Object[]{new MyClass2()}
        );
    }
}
72
Ryan Stewart

@dlevには強く反対します。非常に多くの場合、インターフェイスを使用するテストを作成することをお勧めします。インターフェイスは、クライアントと実装の間のコントラクトを定義します。多くの場合、すべての実装がまったく同じテストに合格する必要があります。明らかに、各実装は独自のテストを持つことができます。

だから、私は2つの解決策を知っています。

  1. インターフェイスを使用するさまざまなテストで抽象テストケースを実装します。具体的なインスタンスを返す抽象プロテクトメソッドを宣言します。インターフェイスの各実装に必要な回数だけこの抽象クラスを継承し、それに応じて前述のファクトリメソッドを実装します。ここでも、より具体的なテストを追加できます。

  2. テストスイート を使用します。

18
AlexR

私はdlevにも同意しません。具体的な実装の代わりにインターフェースに対してテストを書くことは何の問題もありません。

おそらく、パラメーター化されたテストを使用する必要があります。 TestNG の場合、次のようになります(JUnitで少し工夫されています(パラメーターをテスト関数に直接渡すことができないため):

@DataProvider
public Object[][] dp() {
  return new Object[][] {
    new Object[] { new MyImpl1() },
    new Object[] { new MyImpl2() },
  }
}

@Test(dataProvider = "dp")
public void f(MyInterface itf) {
  // will be called, with a different implementation each time
}
14
Cedric Beust

件名への追加の遅れ、新しいソリューションの洞察の共有

また、いくつかのインターフェイスと抽象クラスの複数の実装の正確性を(JUnitに基づいて)テストする適切かつ効率的な方法を探しています。残念ながら、JUnitの_@Parameterized_テストもTestNGの同等の概念も私の要件に正しく適合しません。aprioriこれらのインターフェイス/抽象の実装のリストがわからないからです存在する可能性のあるクラス。つまり、新しい実装が開発され、テスターがすべての既存の実装にアクセスできない場合があります。したがって、テストクラスに実装クラスのリストを指定させるのは効率的ではありません。

この時点で、このタイプのテストを簡素化するための完全かつ効率的なソリューションを提供していると思われる次のプロジェクトを見つけました。 https://github.com/Claudenw/junit-contracts 基本的に、契約テストクラスの注釈@Contract(InterfaceClass.class)を介して、「契約テスト」の定義を許可します。次に、実装者は、注釈@RunWith(ContractSuite.class)および@ContractImpl(value = ImplementationClass.class);を使用して、実装固有のテストクラスを作成します。エンジンは、ImplementationClassが派生するインターフェイスまたは抽象クラスに対して定義されたすべてのContract Testを探すことにより、ImplementationClassに適用されるコントラクトテストを自動的に適用するものとします。私はまだこのソリューションをテストしていませんが、これは有望に思えます。

次のライブラリも見つけました: http://www.jqno.nl/equalsverifier/ これは、Object.equalsおよびObject.hashcodeコントラクトに特にクラスの適合性を明示するという、より具体的なニーズを満たします。

同様に、 https://bitbucket.org/chas678/testhelpers/src いくつかのJava Object.equals、Object.hashcodeを含む基本契約を検証する戦略を示しています。 Comparable.compare、Serializable:このプロジェクトでは、特定のニーズに合わせて簡単に再現できる単純なテスト構造を使用しています。

さて、今のところはこれで終わりです。この投稿を、他の役立つ情報で更新します。

10
jwatkins

私は一般的に、インターフェースがユニットをどのようにしたいのかという単純な理由で、インターフェースに対して単体テストを書くことを避けます機能を定義しません。実装者に構文上の要件を課しますが、それだけです。

逆に、単体テストは、期待する機能が特定のコードパスに存在することを確認することを目的としています。

そうは言っても、この種のテストが意味をなす場合があります。これらのテストで、(指定されたインターフェイスを共有する)作成したクラスが実際に同じ機能を共有することを確認したい場合、最初のオプションをお勧めします。実装するサブクラスでテストプロセスに自分自身を注入するのが最も簡単になります。また、あなたの「詐欺」は本当に真実だとは思いません。実際にテスト中のクラスに独自のモックを提供できない理由はありません(ただし、異なるモックが本当に必要な場合は、インターフェイステストがいずれにせよ均一ではないことを示唆しています)

5
dlev

with Java 8私はこれを行う

public interface MyInterfaceTest {
   public MyInterface createInstance();

   @Test
   default void testMyMethod_True() {
       MyInterface instance = createInstance();
       assertTrue(instance.myMethod(true));
   }

   @Test
   default void testMyMethod_False() {
       MyInterface instance = createInstance();
       assertFalse(instance.myMethod(false));
   }
}

public class MyClass1Test implements MyInterfaceTest {
    public MyInterface createInstance() {
        return new MyClass1();
    }
}

public class MyClass2Test implements MyInterfaceTest {
   public MyInterface createInstance() {
       return new MyClass2();
   }

   @Disabled
   @Override
   @Test
   public void testMyMethod_True() {
       MyInterfaceTest.super.testMyMethod_True();
   };
}
1
Xavier Gouraud