【概要】
LinkedHashMapのデータ登録順が期待値と実測値で異なっていても、同じキーと値が登録されていればデータ登録順の比較はされず、assertEqualsメソッドのテストが成功してしまうことを確認します。
LinkedHashMapはデータ登録順が保たれるので、assertEqualsメソッドでは登録順も比較されテスト失敗すると予想していましたが予想とは異なりました。
【環境】
Java 21
JUnit 5.10.1
【コード】
下記コードは上記GitHubにアップロードしているコードと同じです。
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
class HogeTest {
@Test
void LinkedHashMapのデータ登録順が同じであること() {
// 期待値の作成
Map<String, String> expected = new LinkedHashMap<>();
// 意図的に実測値とはデータ登録順を逆にしてテストが失敗することを予想するが、実行するとテスト成功してしまう
expected.put("b1", "b2");
expected.put("a1", "a2");
// 実測値の作成
Map<String, String> actual = new LinkedHashMap<>();
actual.put("a1", "a2");
actual.put("b1", "b2");
// 確認
assertEquals(expected, actual);
}
@Test
void LinkedHashMapをArrayListに変換してデータ登録順が同じであること() {
// 期待値の作成
Map<String, String> expected = new LinkedHashMap<>();
// 意図的に実測値とはデータ登録順を逆にしてテストが失敗することを予想し、実行するとテスト失敗する
expected.put("b1", "b2");
expected.put("a1", "a2");
// 実測値の作成
Map<String, String> actual = new LinkedHashMap<>();
actual.put("a1", "a2");
actual.put("b1", "b2");
// 確認
assertEquals(new ArrayList<>(expected.entrySet()), new ArrayList<>(actual.entrySet()));
}
}
【テスト結果】
テストケース | テスト結果 | 予想との乖離 |
---|---|---|
LinkedHashMapのデータ登録順が同じであること | テスト成功 | 予想と違った |
LinkedHashMapをArrayListに変換してデータ登録順が同じであること | テスト失敗 | 予想通り |
【なぜデータ登録順が違ってもassertEqualsでテスト成功するのか】
assertEqualsメソッドの呼び出し先では下記コードが実行され、obj1.equals(obj2)
を実行していることが分かります。
static boolean objectsAreEqual(Object obj1, Object obj2) {
if (obj1 == null) {
return obj2 == null;
} else {
return obj1.equals(obj2);
}
}
LinkedHashMapのequalsメソッドはAbstractMapクラスで定義されており、下記コードが実行されます。
for文の中身を見るとデータ登録順を考慮しておらず、ただ同じキーに対して同じ値が入っていることを確認しているだけです。そのためデータ登録順が違ってもassertEqualsでテスト成功します。
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map<?, ?> m))
return false;
if (m.size() != size())
return false;
try {
for (Entry<K, V> e : entrySet()) {
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key) == null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException | NullPointerException unused) {
return false;
}
return true;
}
【データ登録順まで比較する方法】
すでに上記テストコード内に記載しましたが、ArrayListに変換すればデータ登録順を比較することができます。
assertEquals(new ArrayList<>(expected.entrySet()), new ArrayList<>(actual.entrySet()));
【参考サイト】