Hamcrest란?
Hamcrest는 JUnit 기반의 단위 테스트에서 사용할 수 있는 Assertion Framework입니다.
JUnit에서도 Assertion을 위한 다양한 메서드를 지원하지만 Hamcrest는 다음과 같은 이유로 JUnit에서 지원하는 Assertion 메서드보다 더 많이 사용됩니다.
- Assertion을 위한 매쳐(Matcher)가 자연스러운 문장으로 이어지므로 가독성이 향상된다.
- 테스트 실패 메시지를 이해하기 쉽다.
- 다양한 Matcher를 제공한다.
JUnit Assertion을 사용한 단위 테스트에 Hamcrest Assertion 적용해 보기
Junit → Hamcrest 예 1
✅ JUnit에서의 Assertion
import static org.junit.jupiter.api.Assertions.assertEquals;
public class HelloJunitTest {
@DisplayName("Hello Junit Test")
@Test
public void assertionTest1() {
String actual = "Hello, JUnit";
String expected = "Hello, JUnit";
assertEquals(expected, actual); // (1)
}
}
✅ Hamcrest에서의 Assertion
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
public class HelloHamcrestTest {
@DisplayName("Hello Junit Test using hamcrest")
@Test
public void assertionTest1() {
String expected = "Hello, JUnit";
String actual = "Hello, JUnit";
assertThat(actual, is(equalTo(expected))); // (1)
}
}
(1)에서 Hamcrest의 is(), equalTo() Matcher를 사용
- JUnit Assertion 기능 이용
- assertEquals(expected, actual);
- 파라미터로 입력된 값의 변수 이름을 통해 대략적으로 어떤 검증을 하려는지 알 수 있으나 구체적인 의미는 유추를 하는 과정이 필요합니다.
- assertEquals(expected, actual);
- Hamcrest의 매쳐(Matcher) 이용
- assertThat(actual, is(equalTo(expected)));
- (1)의 Assertion 코드 한 줄은 ‘assert that actual is equal to expected’라는 하나의 영어 문장으로 자연스럽게 읽힙니다.
- 굳이 한글로 번역하자면, ‘결과 값(actual)이 기대 값(expected)과 같다는 것을 검증(Assertion)한다.’ 정도로 해석할 수 있습니다.
- assertThat() 메서드의 파라미터
- 첫 번째 파라미터는 테스트 대상의 실제 결과 값입니다.
- 두 번째 파라미터는 기대하는 값입니다. 즉, 이런 값일 거라고 기대(예상)하는 값입니다.
- (1)의 Assertion 코드 한 줄은 ‘assert that actual is equal to expected’라는 하나의 영어 문장으로 자연스럽게 읽힙니다.
- assertThat(actual, is(equalTo(expected)));
Junit → Hamcrest 예 2
✅ JUnit에서의 Assertion
package com.codestates.basic;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class HelloJunitTest {
@DisplayName("Hello Junit Test")
@Test
public void assertionTest1() {
String actual = "Hello, JUnit";
String expected = "Hello, World";
assertEquals(expected, actual);
}
}
테스트 케이스 실행 결과는 “failed”입니다.
실행 결과 메시지가 어떻게 출력되는지
expected: <Hello, World> but was: <Hello, JUnit>
Expected :Hello, World
Actual :Hello, JUnit
✅ Hamcrest에서의 Assertion
package com.codestates.hemcrest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
public class HelloHamcrestTest {
@DisplayName("Hello Junit Test using hamcrest")
@Test
public void assertionTest() {
String expected = "Hello, World";
String actual = "Hello, JUnit";
assertThat(actual, is(equalTo(expected)));
}
}
Hemcrest의 매쳐(Matcher)를 사용했으며, 테스트 케이스 실행 결과는 역시 “failed”입니다.
실행 결과 메시지가 어떻게 출력되는지 확인
Expected: is "Hello, World"
but: was "Hello, JUnit"
이처럼 Hemcrest의 Matcher를 사용해서 사람이 읽기 편한 자연스러운 Assertion 문장을 구성할 수 있으며, 실행 결과가 “failed”일 경우 역시 자연스러운 “failed” 메시지를 확인할 수 있기 때문에 가독성이 상당히 높아집니다.
JUnit → Hamcrest 예 3
package com.codestates;
import java.util.HashMap;
import java.util.Map;
public class CryptoCurrency {
public static Map<String, String> map = new HashMap<>();
static {
map.put("BTC", "Bitcoin");
map.put("ETH", "Ethereum");
map.put("ADA", "ADA");
map.put("POT", "Polkadot");
}
}
✅ JUnit에서의 Assertion
package com.codestates.basic;
import com.codestates.CryptoCurrency;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class AssertionNullHamcrestTest {
@DisplayName("AssertionNull() Test")
@Test
public void assertNotNullTest() {
String currencyName = getCryptoCurrency("ETH");
assertNotNull(currencyName, "should be not null");
}
private String getCryptoCurrency(String unit) {
return CryptoCurrency.map.get(unit);
}
}
✅ Hamcrest에서의 Assertion
package com.codestates.hemcrest;
import com.codestates.CryptoCurrency;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
public class AssertionNullHamcrestTest {
@DisplayName("AssertionNull() Test")
@Test
public void assertNotNullTest() {
String currencyName = getCryptoCurrency("ETH");
assertThat(currencyName, is(notNullValue())); // (1)
// assertThat(currencyName, is(nullValue())); // (2)
}
private String getCryptoCurrency(String unit) {
return CryptoCurrency.map.get(unit);
}
}
‘currencyName is not Null Value.’와 같이 가독성 좋은 하나의 문장처럼 구성이 되는 것을 볼 수 있습니다.
만약 (2)를 주석 해제하면, 아래와 같은 “failed” 메시지를 확인할 수 있습니다.
Expected: is null
but: was "Ethereum"
“failed” 메시지를 하나의 문장으로 풀어보면, ‘Expected is null, but was “Ethereum”’ 같이 자연스러운 문장이 되는 것을 확인할 수 있습니다.
JUnit → Hamcrest 예 4
✅ JUnit에서의 Assertion
package com.codestates.basic;
import com.codestates.CryptoCurrency;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class AssertionExceptionTest {
@DisplayName("throws NullPointerException when map.get()")
@Test
public void assertionThrowExceptionTest() {
assertThrows(NullPointerException.class, () -> getCryptoCurrency("XRP"));
}
...
...
private String getCryptoCurrency(String unit) {
return CryptoCurrency.map.get(unit).toUpperCase();
}
}
JUnit의 assertThrows()를 이용해서 XRP 암호 화폐가 map에 존재하는지(null이 아닌지) 여부를 던져지는 예외(Exception) 발생 여부로 테스트
✅ Hamcrest에서의 Assertion
package com.codestates.hemcrest;
import com.codestates.CryptoCurrency;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class AssertionExceptionHamcrestTest {
@DisplayName("throws NullPointerException when map.get()")
@Test
public void assertionThrowExceptionTest() {
Throwable actualException = assertThrows(NullPointerException.class,
() -> getCryptoCurrency("XRP")); // (1)
assertThat(actualException.getClass(), is(NullPointerException.class)); // (2)
}
private String getCryptoCurrency(String unit) {
return CryptoCurrency.map.get(unit).toUpperCase();
}
}
그런데, 예외에 대한 테스트는 Hamcrest 만으로 Assertion을 구성하기 힘들기 때문에 (1)과 같이 JUnit의 assertThrows() 메서드를 이용해서 assertThrows()의 리턴 값을 전달받은 후에 (2)와 같이 assertThat(actualException.getClass(), is(NullPointerException.class)); 을 통해 throw 된 Exception 타입이 기대했던 Exception 타입과 일치하는지 추가로 검증을 진행했습니다.
코드 3-200의 테스트 케이스 실행 결과는 (1)에서 1차적으로 NullPointException이 발생하므로 (1)의 Assertion 결과는 “passed”이고, (2)에서 결과 값인 actualException.getCause()가 null 이므로, (2)의 Assertion 결과 역시 “passed”입니다.
만약 Hamcrest 만으로 던져진 예외를 테스트하기 위해서는 Custom Matcher를 직접 구현해서 사용할 수 있습니다.
심화 학습
- Hamcrest에서 지원하는 다양한 매쳐(Matcher)의 사용법을 더 알아보고 싶다면 아래 링크를 참고하세요.
- Hamcrest에서 Custom Matcher를 구현하는 방법을 알아보고 싶다면 아래 링크를 참고하세요.
- ‘Writing custom matchers’ 부분
출처 : 코드스테이츠
'TIL' 카테고리의 다른 글
n+1 문제 (0) | 2023.12.14 |
---|---|
Tags In HTML (0) | 2023.07.05 |
Spring MVC Testing 단위 테스트 (0) | 2023.05.23 |
자료구조 Graph traversal (0) | 2023.05.17 |
자료구조 Tree traversal (0) | 2023.05.17 |