3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

記事投稿キャンペーン 「2024年!初アウトプットをしよう」

【JUnit】LinkedHashMapのデータ登録順はassertEqualsで比較出来ない

Last updated at Posted at 2024-01-14

【概要】

LinkedHashMapのデータ登録順が期待値と実測値で異なっていても、同じキーと値が登録されていればデータ登録順の比較はされず、assertEqualsメソッドのテストが成功してしまうことを確認します。
LinkedHashMapはデータ登録順が保たれるので、assertEqualsメソッドでは登録順も比較されテスト失敗すると予想していましたが予想とは異なりました。

【環境】

Java 21
JUnit 5.10.1

【コード】

下記コードは上記GitHubにアップロードしているコードと同じです。

HogeTest.java
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に変換してデータ登録順が同じであること テスト失敗 予想通り

image.png

【なぜデータ登録順が違ってもassertEqualsでテスト成功するのか】

assertEqualsメソッドの呼び出し先では下記コードが実行され、obj1.equals(obj2)を実行していることが分かります。

AssertionUtils.javaの一部抜粋
    static boolean objectsAreEqual(Object obj1, Object obj2) {
        if (obj1 == null) {
            return obj2 == null;
        } else {
            return obj1.equals(obj2);
        }
    }

LinkedHashMapのequalsメソッドはAbstractMapクラスで定義されており、下記コードが実行されます。
for文の中身を見るとデータ登録順を考慮しておらず、ただ同じキーに対して同じ値が入っていることを確認しているだけです。そのためデータ登録順が違ってもassertEqualsでテスト成功します。

AbstractMap.javaの一部抜粋
    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()));

【参考サイト】

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?