web-dev-qa-db-ja.com

2つのList <E>に同じ順序で同じ要素が含まれていることをJUnitでテストするにはどうすればよいですか?

状況

MyObjectクラスの簡単なJUnitテストを書いています。

MyObjectは、Stringの可変引数をとる静的ファクトリメソッドから作成できます。

MyObject.ofComponents("Uno", "Dos", "Tres");

MyObjectが存在する間、クライアントは、.getComponents()メソッドを使用して、作成されたパラメーターをList <E>の形式で検査できます。 。

myObject.ofComponents(); // -> List<String>: { "Uno", "Dos", "Tres" }

言い換えると、MyObjectは、それをもたらしたパラメーターのリストを記憶し、公開します。この契約の詳細:

  • getComponentsの順序は、オブジェクト作成用に選択された順序と同じです
  • 重複する後続のStringコンポーネントは順番に許可され、保持されます
  • nullの動作は未定義です(他のコードはnullがファクトリーに到達しないことを保証します)
  • オブジェクトのインスタンス化後にコンポーネントのリストを変更する方法はありません

StringのリストからMyObjectを作成し、.getComponents()を介して同じリストを返すことができるかどうかを確認する簡単なテストを書いています。 私はすぐにこれを行いますが、これは現実的なコードパスの距離で発生するはずです

コード

ここで私の試み:


List<String> argumentComponents = Lists.newArrayList("One", "Two", "Three");
List<String> returnedComponents =
    MyObject.ofComponents(
        argumentComponents.toArray(new String[argumentComponents.size()]))
        .getComponents();
assertTrue(Iterables.elementsEqual(argumentComponents, returnedComponents));

質問

  • Google GuavaIterables.elementsEqual()は、ビルドパスにライブラリがある場合、これら2つのリストを比較するのに最適な方法ですか? これは私が苦しんでいることです。 Iterable <E> ..を超えるこのヘルパーメソッドを使用して、サイズを確認し、.equals() ..またはインターネット検索が示唆する他のメソッドの実行を繰り返す必要がありますか?単体テストのリストを比較する標準的な方法は何ですか?

取得したいオプションの洞察

  • メソッドテストは合理的に設計されていますか?私はJUnitの専門家ではありません!
  • .toArray()List <E>をEの可変引数に変換する最良の方法ですか?
71
Robottinosino

障害が発生した場合にはるかに優れた出力が得られるため、Hamcrestの使用を好みます

Assert.assertThat(listUnderTest, 
       IsIterableContainingInOrder.contains(expectedList.toArray()));

報告する代わりに

expected true, got false

報告します

expected List containing "1, 2, 3, ..." got list containing "4, 6, 2, ..."

IsIterableContainingInOrder.contain

Hamcrest

Javadocによると:

検査済みのIterableを1回パスすると一連のアイテムが生成され、それぞれが指定されたアイテムの対応するアイテムに論理的に等しい場合に一致するIterablesのマッチャーを作成します。正の一致の場合、検査されるイテレート可能オブジェクトは、指定されたアイテムの数と同じ長さでなければなりません

したがって、listUnderTestには同じ数の要素が必要であり、各要素は期待される値と順番に一致する必要があります。

51
John B

単純にList#equalsを使用しないのはなぜですか?

assertEquals(argumentComponents, imapPathComponents);

List#equalsの契約

2つのリストは、同じ要素が同じ順序で含まれている場合に等しいと定義されます。

70
assylias

List実装のequals()メソッドは要素ごとの比較を行う必要があるため、

assertEquals(argumentComponents, returnedComponents);

ずっと簡単です。

10
DavW

org.junit.Assert.assertEquals()org.junit.Assert.assertArrayEquals()は仕事をします。

次の質問を回避するには:順序を無視する場合は、設定するすべての要素を配置してから比較します:Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))

ただし、重複を無視するだけで順序を保持する場合は、LinkedHashSetでリストをラップします。

さらに別のヒント。トリックAssert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))は、比較が失敗するまで正常に機能します。この場合、セット内の順序がほとんど予測できないため(少なくとも複雑なオブジェクトの場合)、混乱を招く可能性のあるセットの文字列表現に対するエラーメッセージが表示されます。そのため、私が見つけたトリックは、HashSetの代わりにソートされたセットでコレクションをラップすることです。カスタムコンパレータでTreeSetを使用できます。

9
AlexR

優れたコードの読みやすさのために、 Fest Assertions には assertingリスト の素晴らしいサポートがあります

したがって、この場合、次のようになります。

Assertions.assertThat(returnedComponents).containsExactly("One", "Two", "Three");

または、期待されるリストを配列に作成しますが、上記のアプローチの方がわかりやすいので好まれます。

Assertions.assertThat(returnedComponents).containsExactly(argumentComponents.toArray());
5
crunchdog

assertTrue()/ assertFalse():返されるブール結果をアサートするためだけに使用します

assertTrue(Iterables.elementsEqual(argumentComponents、requestedComponents));

テスト対象のメソッドがboolean値を返すため、Assert.assertTrue()またはAssert.assertFalse()を使用します。
メソッドは、期待される要素を含むListなどの特定のものを返すため、このようにassertTrue()でアサートします:Assert.assertTrue(myActualList.containsAll(myExpectedList)はアンチパターンです。
アサーションを簡単に記述できますが、テストが失敗すると、テストランナーが次のようなことを言うだけなので、デバッグも難しくなります。

trueが期待されますが、実際はfalseです

JUnit4のAssert.assertEquals(Object, Object)またはJUnit 5のAssertions.assertIterableEquals(Iterable, Iterable)equals()toString()の両方が、比較されるオブジェクトのクラス(および深く)でオーバーライドされる場合にのみ使用する

アサーションの等価性テストはequals()に依存しており、テスト失敗メッセージは比較されたオブジェクトのtoString()に依存しているため重要です。
Stringequals()toString()の両方をオーバーライドするため、assertEquals(Object,Object)List<String>をアサートすることは完全に有効です。また、この問題については、JUnitを使用したテストでアサーションを簡単にするだけでなく、オブジェクトの同等性の観点から理にかなっているため、クラスでequals()をオーバーライドする必要があります。
アサーションを簡単にするために、他の方法があります(答えの次のポイントで確認できます)。

グアバはユニットテストアサーションを実行/構築する方法ですか?

Google Guava Iterables.elementsEqual()は、これらの2つのリストを比較するためにビルドパスにライブラリがある場合、最良の方法ですか?

いいえそうではありません。 Guavaは、単体テストアサーションを記述するためのライブラリではありません。
ユニットテストのほとんど(私が考えるすべて)を書くのにそれは必要ありません。

単体テストのリストを比較する標準的な方法は何ですか?

良い習慣として、アサーション/マッチャーライブラリを優先します。

特定のアサーションを実行するようにJUnitを奨励することはできません。これにより、提供される機能が非常に少なく制限されているためです。
要素の順序を許可したい場合もあれば、予想される要素が実際の要素と一致するようにしたい場合もあります...

そのため、HamcrestやAssertJなどの単体テストアサーション/マッチャーライブラリを使用するのが正しい方法です。
実際の答えはHamcrestソリューションを提供します。 AssertJ ソリューションです。

org.assertj.core.api.ListAssert.containsExactly() は必要なものです。実際のグループが指定された値を正確に含み、他に何も記載されていないことを確認します:

実際のグループに、指定された値が正確に含まれていることを確認します。

テストは次のようになります。

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

@Test
void ofComponent_AssertJ() throws Exception {
   MyObject myObject = MyObject.ofComponents("One", "Two", "Three");
   Assertions.assertThat(myObject.getComponents())
             .containsExactly("One", "Two", "Three");
}

AssertJの良い点は、予想どおりListを宣言する必要がないことです。これにより、アサーションがよりストレートになり、コードが読みやすくなります。

Assertions.assertThat(myObject.getComponents())
         .containsExactly("One", "Two", "Three");

そして、テストが失敗した場合:

// Fail : Three was not expected 
Assertions.assertThat(myObject.getComponents())
          .containsExactly("One", "Two");

次のような非常に明確なメッセージが表示されます。

Java.lang.AssertionError:

期待:

<["One"、 "Two"、 "Three"]>

正確に(そして同じ順序で)含めるには:

<["One"、 "Two"]>

しかし、いくつかの要素は予期されていませんでした:

<["Three"]>

アサーション/マッチャーライブラリは必須です。これらは本当にさらに進むからです

MyObjectStringsではなくFoosインスタンスを格納するとします:

public class MyFooObject {

    private List<Foo> values;
    @SafeVarargs
    public static MyFooObject ofComponents(Foo... values) {
        // ...
    }

    public List<Foo> getComponents(){
        return new ArrayList<>(values);
    }
}

それは非常に一般的なニーズです。 AssertJを使用しても、アサーションの記述は簡単です。要素のクラスがequals()/hashCode()をオーバーライドしない場合でも、リストの内容が等しいと断言できますが、JUnitの方法ではそうする必要があります。

import org.assertj.core.api.Assertions;
import static org.assertj.core.groups.Tuple.tuple;
import org.junit.jupiter.api.Test;

@Test
void ofComponent() throws Exception {
    MyFooObject myObject = MyFooObject.ofComponents(new Foo(1, "One"), new Foo(2, "Two"), new Foo(3, "Three"));

    Assertions.assertThat(myObject.getComponents())
              .extracting(Foo::getId, Foo::getName)
              .containsExactly(Tuple(1, "One"),
                               Tuple(2, "Two"),
                               Tuple(3, "Three"));
}
3
davidxxx
  • Iterables.elementsEqualが最良の選択かどうかについての私の答え:

Iterables.elementsEqualは2 Listsを比較するのに十分です。

Iterables.elementsEqualはより一般的なシナリオで使用され、より一般的なタイプIterableを受け入れます。つまり、ListSetを比較することもできます。 (順序を繰り返すことで、重要です)

確かにArrayListLinkedListを定義することは非常に適切であり、直接equalsを呼び出すことができます。十分に定義されていないリストを使用する場合は、Iterables.elementsEqualが最適です。 1つのことに注意してください:Iterables.elementsEqualnullを受け入れません

  • リストを配列に変換するには:Iterables.toArrayが使いやすいです。

  • 単体テストの場合、テストケースに空のリストを追加することをお勧めします。