Hamcrest Custom Matcher

Hamcrest에서 Custom Matcher를 구현하는 방법을 소개합니다.

Hamcrest는 containsString(), closeTo() 등과 같이 다양한 Matcher를 제공합니다. 하지만, 기본적으로 제공하는 Matcher들로 구현하기 어려운 테스트 케이스가 있습니다. Hamcrest는 다음과 같은 abstract 클래스를 제공하며, 이 클래스를 구현하여 Custom Matcher를 만들 수 있습니다.

  • TypeSafeMatcher
  • TypeSafeDiagnosingMatcher

프로젝트 의존성 설정

프로젝트가 Gradle을 사용하는 경우, build.gradle에서 아래와 같이 java-hamcrest를 추가합니다.

dependencies {
    testImplementation 'org.hamcrest:java-hamcrest:2.0.0.0'
}

Custom Matcher 구현 (TypeSafeMatcher)

TypeSafeMatcher를 구현하여 Custom Matcher를 만들 수 있습니다.

TypeSafeMatcher를 상속하면 아래 두개 메소드를 오버라이드 해야 합니다.

public class IsInteger extends TypeSafeMatcher<String> {
  @Override
  protected boolean matchesSafely(String item) {
      // return true if it matches
  }

  @Override
  public void describeTo(Description description) {
      // description for failure message
  }
}
  • matchesSafely : 인자로 전달되는 item이 어떤 조건에 일치하는지를 boolean으로 리턴합니다. true가 리턴되면 테스트는 패스합니다.
  • describeTo : 테스트가 실패할 때 보여지는 내용으로, 테스트가 성공하려면 이 객체가 어떤 값이 되어야 하는지에 대한 메시지를 설정합니다.

문자열이 Integer인지 테스트하는 Matcher

아래 IsInteger 클래스는 문자열이 Integer인지 확인하는 Matcher입니다.

matchesSafely()에서는 단순히 Integer.parseInt()으로 Integer인지 아닌지 체크하였습니다. describeTo()에서는 문자열이 Integer로 구성되어야 한다는 메시지를 입력하였습니다.

public class IsInteger extends TypeSafeMatcher<String> {
    @Override
    protected boolean matchesSafely(String str) {
        try {
            Integer.parseInt(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("A String of Integer");
    }

    public static Matcher<String> isInteger() {
        return new IsInteger();
    }
}

위에서 구현한 Matcher는 다음과 같이 assertThat과 함께 사용할 수 있습니다. 두번째 인자로 Matcher를 전달할 때, new IsInteger()로 Matcher 객체를 생성하여 전달하면 됩니다.

@Test
public void testCustomMatcher_isInteger1() {
    String num = "1234";

    assertThat(num, new IsInteger());
}

객체를 생성하여 전달하는 가독성이 좋지 않기 때문에, 다음과 같이 Custom Matcher 객체를 생성하여 리턴하는 static 메소드를 IsInteger 클래스를 추가하였습니다.

public static Matcher<String> isInteger() {
    return new IsInteger();
}

이제 이 메소드로 아래와 같이 간결하게 Custom Matcher를 assertThat()에 전달할 수 있습니다.

@Test
public void testCustomMatcher_isInteger2() {
    String num = "1234";

    assertThat(num, isInteger());
}

실패 메시지

아래 테스트에서 1234.56은 Integer가 아니기 때문에 실패하게 됩니다.

@Test
public void testCustomMatcher_isInteger3() {
    String notNum = "1234.56";

    assertThat(notNum, isInteger());
}

테스트가 실패하면 아래와 같은 실패 메시지를 출력합니다. Expected: 다음에 출력되는 메시지는 describeTo()에서 입력한 문자열이 됩니다.

Expected: A String of Integer
     but: was "1234.56"

다른 Matcher와 함께 사용

Custom Matcher는 is(), not() 등의 기본적으로 제공하는 Matcher들과 함께 사용할 수 있습니다.

@Test
public void testCustomMatcher_isInteger4() {
    String notNum = "1234.56";

    assertThat(notNum, not(isInteger()));
}

Custom Matcher 구현 (TypeSafeDiagnosingMatcher)

이번에는 TypeSafeDiagnosingMatcher 클래스를 구현하여 Custom Matcher를 만들 것입니다. TypeSafeDiagnosingMatcher는 TypeSafeMatcher보다 좀 더 자세한 실패 메시지를 설정할 수 있습니다.

Integer가 짝수인지 테스트하는 Matcher

이번에는 Integer가 짝수인지 확인하는 IsEven이라는 Matcher를 구현합니다.

public class IsEven extends TypeSafeDiagnosingMatcher<Integer> {
    @Override
    protected boolean matchesSafely(Integer integer, Description description) {
        description.appendText("was ").appendValue(integer)
                .appendText(", which is an Odd number");
        return integer % 2 == 0;
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("An Even Number");
    }

    public static Matcher<Integer> isEven() {
        return new IsEven();
    }
}

TypeSafeDiagnosingMatcher의 경우, 아래 두개의 메소드를 구현하면 됩니다. TypeSafeMatcher는 matchesSafely()의 인자로 Description을 전달하지 않지만, 여기서는 전달합니다.

  • matchesSafely(Integer integer, Description description)
  • describeTo(Description description)

matchesSafely()의 구현을 보면, description에 메시지를 입력합니다. 이 메시지는 테스트가 실패할 때만 출력됩니다.

@Override
protected boolean matchesSafely(Integer integer, Description description) {
    description.appendText("was ").appendValue(integer)
            .appendText(", which is an Odd number");
    return integer % 2 == 0;
}

describeTo()는 TypeSafeMatcher처럼 구현하면 됩니다.

실패 메시지

아래 테스트는 13이 홀수이기 때문에, 실패하게 됩니다.

@Test
public void testCustomMatcher_isEven2() {
    Integer num2 = 13;

    assertThat(num2, isEven());
}

테스트해보면, 실패 메시지는 다음과 같이 출력됩니다. matchesSafely()에서 설정한 메시지가 was <13>, which is an Odd number 처럼 출력되는 것을 확인할 수 있습니다.

Expected: An Even Number
     but: was <13>, which is an Odd number
Loading script...

Related Posts

codechachaCopyright ©2019 codechacha