본문 바로가기
Spring/Test

Hamcrest 라이브러리 Matcher를 활용하여 JUnit 테스트 가독성 향상하기

by 흑시바 2023. 1. 29.

JUnit 테스트의 가독성을 높일 수 있도록 Hamcrest에서 제공하는 몇 가지 Matcher 활용법에 대해 소개한다.

0. 준비


우선, 최신 버전의 hamcrest 라이브러리를 받는다. (현재 최신 버전은 2.2)

참고로 스프링에서는 기본적으로 assertj와 함께 해당 라이브러리를 제공한다.

 


  
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>

 

그다음 assertThat + hamcrestMatcher 조합을 통해 가독성 있는 테스트를 해본다.

assertThat의 정적 메서드 파라미터의 첫 번째 인자는 검증하고자 하는 값, 두 번째는 Matcher를 사용한다.

* 여기서 assertThat은 hamcrest 패키지에 있는 assertThat을 사용한다.

 


  
public static <T> void assertThat(T actual, Matcher<? super T> matcher) {
assertThat("", actual, matcher);
}

1. equalTo


equalTo는 해당 Matcher는 비교 기준으로 equals() 메서드를 사용해서 '실제 값이 예상 값과 동일한지 여부를 확인'한다.

 


  
@Test
void testEqualTo() {
Shiba shiba = Shiba.builder()
.name("흑시바")
.build();
assertThat(shiba.getName(), equalTo("흑시바"));
}

 

만약 실제 값이 예상 값과 서로 다르면 테스트가 실패하며 AssertionError 메시지를 통해 왜 실패했는지 확인할 수 있다.

 


  
@Test
void testEqualTo() {
Shiba shiba = Shiba.builder()
.name("흑시바")
.build();
assertThat(shiba.getName(), equalTo("적시바"));
}

 

equalTo는 동등성 비교로 테스트 하기 때문에 특정 필드로 두 개의 객체가 동일하다고 판단하는 경우(값 객체 등)는 equals를 별도로 구현하지 않으면 테스트에 실패하게 된다.

 

예를 들어, 만약 id가 동일한 Shiba 객체가 동일하다고 판단하는데 equals를 구현하지 않는다면 하기 테스트처럼 실패하게 된다.

 


  
@Test
void testEqualToObjet() {
Shiba shiba1 = Shiba.builder()
.id(1L)
.name("흑시바")
.build();
Shiba shiba2 = Shiba.builder()
.id(1L)
.name("흑시바")
.build();
assertThat(shiba1, equalTo(shiba2));
}

equals 비교상으로 동일한 객체가 아니므로 테스트가 실패하게 된다.

 

그러므로 특정 필드로 객체의 동등성을 판단하는 경우에는 equals를 구현하고 테스트를 진행하자.

 


 

equalTo는 또한 '자바 배열 혹은 컬렉션 객체를 비교할 때'도 유용하게 사용할 수 있다.

 

- 비교하는 배열 혹은 컬렉션의 값이 일치하는 경우, 테스트에 성공한다.

 


  
@Test
void testEqualToArray() {
String[] shiba1 = {"시바견", "귀여운", "볼살"};
String[] shiba2 = {"시바견", "귀여운"};
assertThat(shiba1, equalTo(shiba2));
}
@Test
void testEqualToList() {
List<String> shiba1 = Arrays.asList("시바견", "귀여운", "볼살");
List<String> shiba2 = Arrays.asList("시바견", "귀여운", "볼살");
assertThat(shiba1, equalTo(shiba2));
}

 

- 비교하는 배열 혹은 컬렉션의 값이 다른 경우에는, 테스트에 실패한다.

 


  
@Test
void testFailEqualToArray() {
String[] shiba1 = {"시바견", "귀여운", "볼살"};
String[] shiba2 = {"시바견", "귀여운"};
assertThat(shiba1, equalTo(shiba2));
}

  
@Test
void testFailEqualToList() {
List<String> shiba1 = Arrays.asList("시바견", "귀여운", "볼살");
List<String> shiba2 = Arrays.asList("시바견", "귀여운");
assertThat(shiba1, equalTo(shiba2));
}

AssertionError가 발생하며 테스트에 실패한다.

2. is


is는 '단순하게 가독성을 높이는 역할을 수행'한다.

단지 넘겨받은 Matcher를 반환하기만 하고 아무것도 하지 않는 코드이다.

 


  
@Test
void testEqualToObjet() {
Shiba shiba1 = Shiba.builder()
.id(1L)
.name("흑시바")
.build();
Shiba shiba2 = Shiba.builder()
.id(1L)
.name("흑시바")
.build();
assertThat(shiba1, is(equalTo(shiba2)));
}

 

3. not


'어떤 것을 부정하는 단언을 만드는 경우' not 매처를 사용한다.

 


  
@Test
void testNotMatcher() {
List<String> shiba1 = Arrays.asList("시바견", "귀여운", "볼살");
List<String> shiba2 = Arrays.asList("시바견", "귀여운");
assertThat(shiba1, not(equalTo(shiba2)));
}

 

4. nullValue, notNullValue


'null 값이거나 null 값이 아닌 값을 검사하는 경우' 사용한다.

 


  
@Test
void testNullValueMatcher() {
assertThat(null, nullValue());
}
@Test
void testNotNullValueMatcher() {
assertThat("흑시바", notNullValue());
}

 

5. startsWith


'특정 문자열로 시작하는지 검사하는 경우' 사용한다.

 


  
@Test
void testStartsWithMatcher() {
assertThat("shiba-holic", startsWith("shiba"));
}

 

6. closeTo


'부동소수점 수를 비교하는 경우' 사용한다.

 

자바에서 부동소수점 타입(float, double)으로 비교할 때 근사치로 구해야하는 값들이 존재한다.

따라서, 일반적인 equalTo Matcher를 사용해서 비교하는 경우 테스트에 실패할 수 있다.

 


  
@Test
void testFailNumberTest() {
assertThat(7.33*6, equalTo(43.98));
}

오차범위로 인해 테스트가 실패한다.

 

이런 경우, closeTo Matcher를 사용해서 두 수가 벌어질 수 있는 오차의 허용 범위를 지정해야 한다.

 


  
@Test
void testCloseToMatcher() {
assertThat(7.33*6, closeTo(43.98, 0.0004));
}

 

7. sameInstance


'동일한 인스턴스인지 확인할 때' 사용한다.

 


  
@Test
void testSameInstance() {
Shiba shiba1 = Shiba.builder().name("흑시바").build();
Shiba shiba2 = Shiba.builder().name("흑시바").build();
assertThat(shiba1, not(sameInstance(shiba2)));
}

 

8. allOf


'모든 Matcher 값이 일치하는지 여부를 확인할 때' 사용한다.

 


  
@Test
void testallOf() {
List<String> shiba1 = Arrays.asList("시바견", "귀여운", "사랑스러운");
assertThat(shiba1, allOf(hasItem("시바견"), hasItem("귀여운")));
}

 

9. anyOf


'하나 이상의 Matcher 값이 일치하는지 여부를 확인할 때' 사용한다.

 


  
@Test
void testanyOf() {
List<String> shiba1 = Arrays.asList("시바견", "귀여운", "사랑스러운");
assertThat(shiba1, anyOf(hasItem("흑시바"), hasItem("는"), hasItem("시바견")));
}

 

10. equalToIgnoringCase


'대소문자 구분 없이 동일한 문자열인지 판단할 때' 사용한다.

 


  
@Test
void testEqualToIgnoringCase() {
String testString = "Shiba is Black";
assertThat(testString, equalToIgnoringCase("shiba is black"));
}

 

11. either


'여러 Matcher 중 일치하는 Matcher가 있는지 확인할 때' 사용한다. or를 활용할 때 유용하다.

 


  
@Test
void testEqualToIgnoringCase() {
String testString = "Shiba is Black";
assertThat(testString, either(containsString("Red"))
.or(containsString("Shiba"))
.or(containsString("Color")));
}

 

12. both


'여러 Matcher가 동시에 일치하는지 확인할 때' 사용한다. and를 활용할 때 유용하다.

 


  
@Test
void testBoth() {
String testString = "Shiba is Black";
assertThat(testString, both(containsString("Shiba"))
.and(containsString("Black")));
}

 

REFERENCE

https://www.javadoc.io/doc/org.hamcrest/hamcrest/latest/org/hamcrest/Matchers.html

 

Matchers - hamcrest 2.2 javadoc

Latest version of org.hamcrest:hamcrest https://javadoc.io/doc/org.hamcrest/hamcrest Current version 2.2 https://javadoc.io/doc/org.hamcrest/hamcrest/2.2 package-list path (used for javadoc generation -link option) https://javadoc.io/doc/org.hamcrest/hamcr

www.javadoc.io

 

댓글