Java 순서를 무시한 두 List의 Equality
1. 설정
List#equals Java 문서에 따라 두 List가 동일한 순서로 동일한 요소를 포함하는 경우 동일하다. 따라서 우리는 순서에 구애받지 않는 비교를 원하기 때문에 단순히 equals 메서드를 사용할 수 없다.
다음 세 가지 list를 테스트를 위한 예제 입력으로 사용한다.
1
2
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);
2. JUnit 사용
JUnit은 Java 생태계에서 단위 테스트에 사용되는 잘 알려진 프레임워크이다.
아래 논리를 사용하여 assertTrue
및 assertFalse
메서드를 사용하여 두 List의 동등성을 비교할 수 있다.
여기에서 두 List의 크기를 확인하고 첫 번째 List가 두 번째 List의 모든 요소를 포함하는지 또는 그 반대인지 확인한다. 이 솔루션은 작동하지만 읽기가 쉽지 않다. 이제 몇 가지 대안이 있다.
1
2
3
4
@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeTrue() {
assertTrue(first.size() == second.size() && first.containsAll(second) && second.containsAll(first));
}
이 첫 번째 테스트에서는 두 List의 요소가 동일한지 확인하기 전에 두 List의 크기를 비교한다. 이 두 조건이 모두 true를 반환하면 테스트가 통과된다.
이제 실패한 테스트이다.
1
2
3
4
@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeFalse() {
assertFalse(first.size() == third.size() && first.containsAll(third) && third.containsAll(first));
}
대조적으로 이 버전의 테스트에서는 두 List의 크기가 동일하더라도 모든 요소가 일치하지 않다.
3. AssertJ 사용
AssertJ는 Java 테스트에서 유창하고 풍부한 어설션을 작성하기 위한 오픈 소스 커뮤니티 기반 라이브러리이다.
Maven 프로젝트에서 사용하기 위해 pom.xml
파일에 assertj-core
종속성을 추가한다.
1
2
3
4
5
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.16.1</version>
</dependency>
동일한 요소와 크기의 두 List 인스턴스가 같은지 비교하는 테스트를 작성한다.
1
2
3
4
@Test
void whenTestingForOrderAgnosticEqualityBothList_ShouldBeEqual() {
assertThat(first).hasSameElementsAs(second);
}
이 예제에서 먼저 주어진 iterable의 모든 요소가 순서와 관계없이 포함되어 있는지 확인한다. 이 접근 방식의 주요 제한 사항은 hasSameElementsAs
메서드가 중복 항목을 무시한다는 것이다.
1
2
3
4
5
6
@Test
void whenTestingForOrderAgnosticEqualityBothList_ShouldNotBeEqual() {
List a = Arrays.asList("a", "a", "b", "c");
List b = Arrays.asList("a", "b", "c");
assertThat(a).hasSameElementsAs(b);
}
이 테스트에서는 요소가 동일하지만 두 List의 크기가 같지 않지만 중복 항목을 무시하므로 어설션은 여전히 참이다.
hasSameElementsAs()
와 관련된 문제를 극복하기 위해 containsExactlyInAnyOrderElementsOf()
를 사용할 수 있다. 이 기능은 두 List에 순서에 관계없이 정확히 동일한 요소가 포함되어 있는지 확인한다.
1
assertThat(a).containsExactlyInAnyOrderElementsOf(b);
테스트는 실제로 예상대로 실패한다.
4. Hamcrest 사용
이미 Hamcrest를 사용 중이거나 단위 테스트 작성에 사용하려는 경우 순서에 구애받지 않는 비교를 위해 Matchers#containsInAnyOrder
메서드를 사용하는 방법은 다음과 같다.
Maven 프로젝트에서 Hamcrest를 사용하기 위해 pom.xml
파일에 hamcrest-all
종속성을 추가한다.
1
2
3
4
5
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
</dependency>
테스트를 살펴본다.
1
2
3
4
@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeEqual() {
assertThat(first, Matchers.containsInAnyOrder(second.toArray()));
}
여기서 containsInAnyOrder
메소드는 Iterables에 대해 검사된 Iterable 요소와 일치하는 순서에 구애받지 않는 매처를 생성한다. 이 테스트는 List의 요소 순서를 무시하고 두 List의 요소를 일치시킨다.
이 솔루션은 이전 섹션에서 설명한 것과 동일한 문제를 겪지 않으므로 명시적으로 크기를 비교할 필요가 없다.
5. Apache Commons 사용
JUnit, Hamcrest 또는 AssertJ 외에 또 다른 라이브러리 또는 프레임워크는 Apache CollectionUtils 이다. 광범위한 사용 사례를 포괄하는 일반적인 작업에 대한 유틸리티 메서드를 제공하고 상용구 코드 작성을 방지하는데 도움이 된다.
Maven 프로젝트에서 사용하기 위해 pom.xml
파일에 commons-collections4
종속성을 추가한다.
1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
다음은 CollectionUtils를 사용한 테스트이다.
1
2
3
4
5
@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeTrueIfEqualOtherwiseFalse() {
assertTrue(CollectionUtils.isEqualCollection(first, second));
assertFalse(CollectionUtils.isEqualCollection(first, third));
}
isEqualCollection
메소드는 주어진 컬렉션이 동일한 카디널리티를 가진 정확히 동일한 요소를 포함하는 경우 true
를 반환한다. 그렇지 않으면 false
를 반환한다.