1. 概要

単体テストを作成するときに、リストの順序にとらわれない比較を行う必要がある場合があります。 この短いチュートリアルでは、このような単体テストを作成する方法のさまざまな例を見ていきます。

2. 設定

List#equals Javaのドキュメントによると、2つのリストに同じ要素が同じ順序で含まれている場合、2つのリストは同じです。 したがって、順序にとらわれない比較を行いたいので、equalsメソッドを単に使用することはできません。

このチュートリアル全体を通して、テストの入力例として次の3つのリストを使用します。

List first = Arrays.asList(1, 3, 4, 6, 8);
List second = Arrays.asList(8, 1, 6, 3, 4);
List third = Arrays.asList(1, 3, 3, 6, 6);

順序にとらわれない比較を行うには、さまざまな方法があります。 それらを一つずつ見ていきましょう。

3. JUnitの使用

JUnit は、Javaエコシステムでの単体テストに使用されるよく知られたフレームワークです。

以下のロジックを使用して、assertTrueメソッドとassertFalseメソッドを使用して2つのリストの同等性を比較できます。

ここでは、両方のリストのサイズをチェックし、最初のリストに2番目のリストのすべての要素が含まれているかどうかを確認します。その逆も同様です。 このソリューションは機能しますが、あまり読みやすくありません。 それでは、いくつかの選択肢を見てみましょう。

@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeTrue() {
    assertTrue(first.size() == second.size() && first.containsAll(second) && second.containsAll(first));
}

この最初のテストでは、両方のリストの要素が同じであるかどうかを確認する前に、両方のリストのサイズが比較されます。 これらの条件の両方がtrueを返すため、テストに合格します。

失敗したテストを見てみましょう。

@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeFalse() {
    assertFalse(first.size() == third.size() && first.containsAll(third) && third.containsAll(first));
}

対照的に、このバージョンのテストでは、両方のリストのサイズは同じですが、すべての要素が一致するわけではありません。

4. AssertJの使用

AssertJ は、Javaテストで流暢で豊富なアサーションを作成するために使用されるオープンソースのコミュニティ主導のライブラリです。

Mavenプロジェクトで使用するには、pom.xmlファイルにassertj-core依存関係を追加しましょう。

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.16.1</version>
</dependency>

同じ要素と同じサイズの2つのリストインスタンスの同等性を比較するテストを書いてみましょう。

@Test
void whenTestingForOrderAgnosticEqualityBothList_ShouldBeEqual() {
    assertThat(first).hasSameElementsAs(second);
}

この例では、 first に、指定されたiterableのすべての要素が含まれ、他には何も含まれていないことを任意の順序で確認します。 このアプローチの主な制限は、hasSameElementsAsメソッドが重複を無視することです。

これを実際に見て、私たちが何を意味するのかを見てみましょう。

@Test
void whenTestingForOrderAgnosticEqualityBothList_ShouldNotBeEqual() {
    List a = Arrays.asList("a", "a", "b", "c");
    List b = Arrays.asList("a", "b", "c");
    assertThat(a).hasSameElementsAs(b);
}

このテストでは、同じ要素がありますが、両方のリストのサイズは等しくありませんが、重複を無視するため、アサーションは引き続き真になります。 それを機能させるには、両方のリストにサイズチェックを追加する必要があります。

assertThat(a).hasSize(b.size()).hasSameElementsAs(b);

両方のリストのサイズのチェックを追加し、その後にメソッド hasSameElementsAs を追加すると、実際に期待どおりに失敗します。

5. ハムクレストの使用

すでにHamcrestを使用している場合、または単体テストの記述に使用したい場合は、 Matchers#containsInAnyOrderメソッドを使用して順序に依存しない比較を行う方法を次に示します。

MavenプロジェクトでHamcrestを使用するには、[X58X]pom.xmlファイルにhamcrest-all依存関係を追加しましょう。

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>

テストを見てみましょう:

@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeEqual() {
    assertThat(first, Matchers.containsInAnyOrder(second.toArray()));
}

ここで、メソッド containsInAnyOrder は、 Iterables の順序にとらわれないマッチャーを作成します。これは、検査されたIterable要素とのマッチングを行います。 このテストは、リスト内の要素の順序を無視して、2つのリストの要素を照合します。

ありがたいことに、このソリューションでは前のセクションで説明したのと同じ問題が発生しないため、サイズを明示的に比較する必要はありません。

6. ApacheCommonsの使用

JUnit、Hamcrest、またはAssertJ以外に使用できる別のライブラリまたはフレームワークは、 ApacheCollectionUtilsです。 幅広いユースケースをカバーする一般的な操作のユーティリティメソッドを提供し、ボイラープレートコードの記述を回避するのに役立ちます。

Mavenプロジェクトで使用するには、pom.xmlファイルにcommons-collections4依存関係を追加しましょう。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

CollectionUtilsを使用したテストは次のとおりです。

@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeTrueIfEqualOtherwiseFalse() {
    assertTrue(CollectionUtils.isEqualCollection(first, second));
    assertFalse(CollectionUtils.isEqualCollection(first, third));
}

isEqualCollection メソッドは、指定されたコレクションに同じカーディナリティを持つまったく同じ要素が含まれている場合、trueを返します。 それ以外の場合は、falseを返します。

7. 結論

この記事では、2つのリストインスタンスの同等性を確認する方法について説明しました。ここでは、両方のリストの要素の順序が異なります。

これらの例はすべて、GitHubにあります。