본문 바로가기
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

 

댓글