Dev/TDD

Junit In Action - TDD를 위한 테스트 원칙, 도구 및 활용 Review - 핵심 애노테이션 ( 2/2 )

린네의 2024. 6. 14. 16:48

 

개발환경

IDE :  intelliJ 
FrameWork : springboot 3.x
Launguage : java 17
TestTool : Junit5 

 

 

예제 소스 링크

 

 

 

HamcrestMatcher 사용하기

Hamcrest 라이브러리는 테스트 프레임워크는 아니지만, 간명한 매치 규칙을 선언하는데 도움을 준다. 매치 규칙은 다양한 상황에 쓰일 수 있지만 특히 단위 테스트에서 유용하게 쓰일 수 있다.

 

매처(Matcher)는 제약(constraint) 혹은 술어(predicate)로 표현되는데 이것을 사용하여 테스트 메서드 안에 있는 복잡한 단언문을 간명하게 처리할 수 있다.

 

아래는 중복 조건을 검사하는 단언문을 assertThat을 통해 처리하는 예제이다.

   @Test
    @DisplayName("Hamcrest를 사용하지 않은 테스트")
    public void testWithoutHamcrest() {
        assertEquals(3, values.size());
        assertTrue(values.contains("Oliver") || values.contains("Jack") || values.contains("Harry"));
    }

    @Test
    @DisplayName("Hamcrest를 사용해서 자세한 실패 정보를 나타내는 테스트")
    public void testListWithHamcrest() {
        assertThat(values, hasSize(3));
        assertThat(values, hasItem(anyOf(equalTo("Oliver"), equalTo("Jack"), equalTo("Harry")))); // Matcher를 사용해서 간명하게 표시
    }

 

사실 assertThat을 쓰냐, 단언문을 여러 개로 쓰냐는 단순히 개발자의 선택이다. 본인이 좀 더 작성하기 편하고, 팀원과 공유할 때 가독성 있는 코드를 보다 편하고 쉽게 전달할 수 있는 방법을 선택하면 된다.

 

이전 글에도 짧게 언급했는데,  Junit5에서는 더 이상 assertThat 메서드를 제공하지 않는다.  따라서 별도로 org.hamcrest.MatcherAssert를 import 해서 사용해야 한다.

 

MatcherAssert에서 제공하는 assertThat에서는 다음과 같이 두 세가지 파라미터를 사용할 수 있다.

 

  • 단언문이 실패했을 때 나타나는 오류 메시지(선택사항)
  • 실제 값 또는 실제 객체
  • 예상 값에 대한 Matcher 객체
//assertThat(실제 객체, 예상값에 대한 Matcher 객체)
assertThat(values, hasItem(anyOf(equalTo("Oliver"), equalTo("Jack"), equalTo("Harry")))); // Matcher를 사용해서 간명하게 표시

 

Matcher 객체를 생성하려면 org.hamcrest.Matchers 클래스에서 제공하는 정적 팩토리 메서드 중 하나를 사용해야 한다.

 

 

  • 일반적인 Hamcrest 정적 팩토리 메서드의 사용 사례
팩터리 메서드 사용 사례
anything 아무것이나 일치하면 될 때 사용한다. 단언문을 더 읽기 쉽게 만들 떄 유용하다.
is 문장의 가독성을 높이고 싶을 때 사용한다. 일종의 장식 표현이다.
allOf 모든 매체 규칙을 만족하는지 확인한다. ( &&연산자와 유사하다.)
anyOf 하나라도 일치하는 매치 규칙이 있는지 확인한다. ( ||연산자와 비슷하다.)
not 매치 규칙의 의미를 뒤집는다 ( ! 연산자와 비슷하다.)
instanceOf 객체가 특정 클래스의 인스턴스인지 확인한다.
sameInstance 객체 동일성을 확인한다.
nullValue, notNullValue null이 null이 아닌지 확인한다.
hasProperty 객체가 특정 속성을 가졌는지 확인한다.
hasEntry, hasKey, hasValue 맵이 특정 엔트리, 키, 값을 가졌는지 확인한다.
hasItem, hasItems 컬렉션이 특정 요소나 요소들을 가졌는지 확인한다.
closeTo, GreaterThan, GreaterThanOrEqualTo, lessThan, lessThanOrEqualTo 주어진 숫자가 가까운지, 큰지, 크거나 같은지, 작은지, 작거나 같은지 확인한다.
equalToIgnoringCase 대소문자를 무시하고 주어진 문자열이 일치하는지 확인한다.
equalToIgnoringWhiteSpace 공백을 무시하고 주어진 문자열이 일치하는지 확인한다.
containString, startsWith, endsWith 주어진 문자열이 특정 문자열을 포함하는지, 특정 문자열로 시작하는지, 특정 문자열로 끝나는지 확인한다.

 

 

 

 

HamcrestMatcher과 AssertJ 비교

 

그런데, 나는 HamcrestMatcher을 사용하지 않는다. ( 생각보다 불편하다! )  대신 assertJ에서 제공하는 assertThat을 쓴다.

둘이 기능은 똑같다. 단지 쓰는 함수명과 문법 차이만 있을 뿐이다.

 

Hamcrest (org.hamcrest.Matcher)

  1. 표현력: Hamcrest는 매처(matchers)를 사용하여 조건을 정의합니다. 이 매처는 주로 정적인 메서드 체인으로 작성됩니다. 예를 들어, assertThat(value, is(equalTo(expectedValue)))와 같은 형태입니다.
  2. 확장성: 사용자가 직접 커스텀 매처를 쉽게 만들 수 있습니다.
  3. 제약적 표현: Hamcrest는 구문과 어휘가 제한적이어서 복잡한 검증 로직을 작성하기가 어려울 수 있습니다.
  4. 통합성: JUnit과 잘 통합되어 있어 널리 사용됩니다.

AssertJ

  1. 표현력: AssertJ는 플루언트 인터페이스를 제공하여 더 자연스럽고 읽기 쉬운 검증을 할 수 있습니다. 예를 들어, assertThat(value). isEqualTo(expectedValue)와 같이 작성할 수 있습니다.
  2. 확장성: AssertJ도 사용자 정의 검증을 쉽게 만들 수 있으며, Hamcrest보다 더 직관적입니다.
  3. 풍부한 기능: AssertJ는 컬렉션, 맵, 예외, 제네릭 타입 등 다양한 객체 타입에 대한 풍부한 검증 메서드를 제공합니다.
  4. 인간 친화적: 에러 메시지가 더 읽기 쉽고 유용한 정보를 제공합니다.

 

 

간단한 비교 예시를 보자

 

 

  • Hamcrest
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

assertThat(actualValue, is(equalTo(expectedValue)));
assertThat(list, hasSize(3));
assertThat(map, hasKey("key1"));

 

 

  • AssertJ
import static org.assertj.core.api.Assertions.assertThat;

assertThat(actualValue).isEqualTo(expectedValue);
assertThat(list).hasSize(3);
assertThat(map).containsKey("key1");

 

둘이 검증하는 것은 똑같지만 사용방법만 다른 것을 알 수 있다. 일반적으로 내 주변에는 주로 AssertJ를 좀 더 많이 쓰는 것 같다.

assertThat을 사용하면 static import시 여러 개가 뜨는데 본인이 사용할 패키지를 알맞게 선택해서 쓰도록 하자.