web-dev-qa-db-ja.com

インターフェイスの複数の実装のための単一の単体テストを書く

Singly Linked List、Douly、Circularなどの実装を含むインターフェイスListがあります。Singly向けに作成したユニットテストは、ほとんどのDoublyだけでなく、Circularおよびその他の新しいインターフェイスの実装にも適しています。では、実装ごとに単体テストを繰り返す代わりに、JUnitには、1つのJUnitテストを実行して別の実装に対して実行できる組み込みの機能がありますか?

JUnitのパラメーター化されたテストを使用して、Singly、double、circularなどの異なる実装を提供できますが、実装ごとに同じオブジェクトを使用してクラス内のすべてのテストを実行します。

51
ChrisOdney

私はおそらくJUnitのパラメータ化されたテスト(IMHOはかなり不器用に実装されています)を避け、テスト実装によって継承できる抽象Listテストクラスを作成します。

public abstract class ListTestBase<T extends List> {

    private T instance;

    protected abstract T createInstance();

    @Before 
    public void setUp() {
        instance = createInstance();
    }

    @Test
    public void testOneThing(){ /* ... */ }

    @Test
    public void testAnotherThing(){ /* ... */ }

}

その後、さまざまな実装が独自の具象クラスを取得します。

class SinglyLinkedListTest extends ListTestBase<SinglyLinkedList> {

    @Override
    protected SinglyLinkedList createInstance(){ 
        return new SinglyLinkedList(); 
    }

}

class DoublyLinkedListTest extends ListTestBase<DoublyLinkedList> {

    @Override
    protected DoublyLinkedList createInstance(){ 
        return new DoublyLinkedList(); 
    }

}

(すべての実装をテストする1つのテストクラスを作成する代わりに)この方法で行うことの良い点は、1つの実装でテストする特定のコーナーケースがある場合、特定のテストサブクラスにさらにテストを追加できることです。 。

52
gustafc

私はこれが古いことを知っていますが、@Parameterをフィールドメンバーに入力して、値を注入します。

私の意見では、それは少しきれいです。

@RunWith(Parameterized.class)
public class MyTest{

    private ThingToTest subject;

    @Parameter
    public Class clazz;

    @Parameters(name = "{index}: Impl Class: {0}")
    public static Collection classes(){
        List<Object[]> implementations = new ArrayList<>();
        implementations.add(new Object[]{ImplementationOne.class});
        implementations.add(new Object[]{ImplementationTwo.class});

        return implementations;
    }

    @Before
    public void setUp() throws Exception {
        subject = (ThingToTest) clazz.getConstructor().newInstance();
    }
3
mcnichol

@ dasblinkenlight および this anwserのanwserに基づいて、共有したいユースケースの実装を思いつきました。

インターフェイスIImporterServiceを実装するクラスに ServiceProviderPatterndifference APIとSPI )を使用します。インターフェースの新しい実装が開発された場合、実装を登録するためにMETA-INF/services /の構成ファイルのみを変更する必要があります。

META-INF/services /内のファイルは、サービスインターフェイスの完全修飾クラス名(IImporterService)にちなんで命名されます。

de.myapp.importer.IImporterService

このファイルには、IImporterServiceを実装するcassesのリストが含まれています。

de.myapp.importer.impl.OfficeOpenXMLImporter

ファクトリクラスImporterFactoryは、クライアントにインターフェイスの具体的な実装を提供します。


ImporterFactoryは、 ServiceProviderPattern を介して登録されたインターフェイスのすべての実装のリストを返します。 setUp()メソッドは、各テストケースで新しいインスタンスが使用されるようにします。

_@RunWith(Parameterized.class)
public class IImporterServiceTest {
    public IImporterService service;

    public IImporterServiceTest(IImporterService service) {
        this.service = service;
    }

    @Parameters
    public static List<IImporterService> instancesToTest() {
        return ImporterFactory.INSTANCE.getImplementations();
    }

    @Before
    public void setUp() throws Exception {
        this.service = this.service.getClass().newInstance();
    }

    @Test
    public void testRead() {
    }
}
_

ImporterFactory.INSTANCE.getImplementations()メソッドは次のようになります。

_public List<IImporterService> getImplementations() {
    return (List<IImporterService>) GenericServiceLoader.INSTANCE.locateAll(IImporterService.class);
}
_
1

テストListを引数に依存する実装の1つのインスタンスに設定するヘルパークラスをテストクラスに実際に作成できます。 this と組み合わせて、あなたが望む振る舞いを得ることができるはずです。

0
LionC

最初の答えを拡張すると、JUnit4のパラメーターの側面は非常にうまく機能します。以下は、フィルターをテストするプロジェクトで使用した実際のコードです。クラスはファクトリー関数(getPluginIO)を使用して作成され、関数getPluginsNamedは、SezPozと注釈を使用して名前を持つすべてのPluginInfoクラスを取得し、新しいクラスを自動的に検出できるようにします。

@RunWith(value=Parameterized.class)
public class FilterTests {
 @Parameters
 public static Collection<PluginInfo[]> getPlugins() {
    List<PluginInfo> possibleClasses=PluginManager.getPluginsNamed("Filter");
    return wrapCollection(possibleClasses);
 }
 final protected PluginInfo pluginId;
 final IOPlugin CFilter;
 public FilterTests(final PluginInfo pluginToUse) {
    System.out.println("Using Plugin:"+pluginToUse);
    pluginId=pluginToUse; // save plugin settings
    CFilter=PluginManager.getPluginIO(pluginId); // create an instance using the factory
 }
 //.... the tests to run

コレクションが実際のパラメーターの配列のコレクションとしてコンストラクター(この場合はPluginInfoと呼ばれるクラス)に渡されることが重要であることに注意してください(この方法で動作する理由は個人的にわかりません)。 wrapCollection静的関数はこのタスクを実行します。

/**
 * Wrap a collection into a collection of arrays which is useful for parameterization in junit testing
 * @param inCollection input collection
 * @return wrapped collection
 */
public static <T> Collection<T[]> wrapCollection(Collection<T> inCollection) {
    final List<T[]> out=new ArrayList<T[]>();
    for(T curObj : inCollection) {
        T[] arr = (T[])new Object[1];
        arr[0]=curObj;
        out.add(arr);
    }
    return out;
}
0
kmader