Junit에서 Hamcrest를 사용하는 이유

Hamcrest는 Junit의 Assert framework입니다. Hamcrest는 다양한 Matcher를 제공하며, 간결한 코드로 객체의 상태를 테스트할 수 있습니다. 또한, 테스트가 실패되었을 때, 어떤 이유로 테스트가 실패하였는지 자세한 Failure 메시지를 출력합니다.

Hamcrest라는 용어는 단순히 Matchers의 알파벳 위치를 변경하여 만들어졌습니다.

Matcher

Matcher는 match operation을 수행하는 객체입니다.

다음과 같이 assertThat()에 Object와 Matcher를 인자로 전달하면, Matcher는 Object가 예상하는 조건에 부합하는지 확인합니다. Matcher의 결과에 따라서 테스트가 성공하거나 실패합니다.

public void assertThat(Object o, Matcher matcher){
    ...
}

Hamcrest를 사용하는 이유

Junit의 Assert를 이용한 테스트와 Hamcrest를 이용한 테스트를 비교하면서, Hamcrest를 사용하는 이유에 대해서 알아보겠습니다.

  • Failure 메시지의 가독성
  • 테스트 코드의 가독성
  • 다양한 Matcher 제공

Failure 메시지의 가독성

아래 코드는 Junit에서 제공하는 기본적인 Assert로 변수 a와 b가 다른지 체크하는 테스트입니다.

@Test
public void test_using_junit() {
    int a = 10;
    int b = 10;

    assertNotEquals(a, b);
}

위 테스트는 당연히 실패하게 되는데, 실패 메시지를 보면 뭐가 잘못되었다는 것인지 한눈에 들어오지 않습니다. 로그와 함께 코드를 보면, a와 b의 값이 같아서 테스트가 실패되었구나 이해하게 됩니다.

expected: not equal but was: <10>
org.opentest4j.AssertionFailedError: expected: not equal but was: <10>

위의 코드는 다음과 같이 Hamcrest를 사용하는 테스트로 구현할 수도 있습니다.

@Test
public void test_using_hamcrest() {
    int a = 10;
    int b = 10;

    assertThat(a, is(not(equalTo(b))));
}

이 테스트도 당연히 실패하게 되는데, 실패 로그를 보면 위의 로그보다 이해하기가 편합니다.

Expected: is not <10>
     but: was <10>

테스트 코드의 가독성 (1)

assertNotEquals()는 Junit에서 기본적으로 제공하는 Assert입니다. 이 Assert는 인자로 전달된 a와 b가 다른지 확인합니다. 간단한 코드이기 때문에 이해하는데 어렵지는 않습니다.

assertNotEquals(a, b);

다음은 Hamcrest를 사용하여 위와 동일한 조건을 체크하는 테스트 코드입니다. 코드를 보면 위와 다르게 완벽한 영어 문장이 됩니다.

assertThat(a, is(not(equalTo(b))));

위의 코드는 지나치게 완벽한 문장으로 만들어져있어서 약간 가독성이 떨어진다고 생각할 수 있는데요. 다음과 같이 is()를 제거해도 결과는 동일하며, 좀 더 코드가 간결해 보입니다.

assertThat(a, not(equalTo(b)));

테스트 코드의 가독성 (2)

다음과 같이 str 변수가 3개의 조건을 모두 만족하는지 테스트하는 코드를 작성할 수 있습니다. 물론, 3개의 assert로 각각 테스트하는 것이 좋지만, 1개의 assert로 구현해야하는 상황이 있다고 가정했습니다.

public void test_allOf() {
    String str = "MyTest";
    boolean result = str.equals("MyTest")
            && str.startsWith("My")
            && str.contains("Test");

    assertTrue(result);
}

Hamcrest에서는 allOf() Matcher를 제공하며, 인자로 전달되는 모든 Matcher가 패스해야 테스트가 성공합니다. 즉, allOf()는 논리 연산자에서 AND를 의미합니다. && 대신에 all Of라는 표현이 들어가서 코드를 이해하는데 도움이 될 수 있습니다.

@Test
public void test_allOf() {
    String str = "MyTest";

    assertThat(str, allOf(is("MyTest"),
                startsWith("My"),
                containsString("Test")));
}

반대로 논리연산자 OR에 해당하는 Matcher는 anyOf()입니다. 인자로 전달되는 Matcher 중에 하나만 패스되면 테스트가 패스됩니다.

assertThat(str, anyOf(is("MyTest"),
            startsWith("Me"),
            containsString("Test")));

다양한 Matcher 제공

a와 b의 절대값 차이가 0.5 이하라는 것을 테스트하려면 다음과 같이 assertTrue로 구현할 수 있습니다.

@Test
public void test_closeTo() {
    double a = 10.9;
    double b = 10.0;

    assertTrue(Math.abs(a-b) < 0.5);
}

하지만 이 코드는 가독성도 좋지 못하고, 실패했을 때 다음과 같이 왜 실패했는지 이해하기 어려운 로그를 출력합니다.

expected: <true> but was: <false>
Expected :true
Actual   :false

Hamcrest에는 closeTo()라는 Matcher를 제공하고, 이것을 이용하여 절대값의 차이가 0.5 이하인지 테스트 할 수 있습니다.

@Test
public void test_using_hamcrest3() {
    double a = 10.9;
    double b = 10.0;

    assertThat(a, closeTo(b, 0.5));
}

테스트가 실패했을 때도, 왜 실패했는지에 대한 로그가 출력됩니다.

Expected: a numeric value within <0.5> of <10.0>
     but: <10.9> differed by <0.40000000000000036> more than delta <0.5>

Hamcrest에는 closeTo() 외에도 다양한 Matcher들을 제공합니다. API에 대한 자세한 내용은 Hamcrest JavaDoc을 참고하시면 됩니다.

  • allOf, anyOf
  • not, is
  • hasEntry, hasKey, hasValue
  • closeTo
  • greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo
  • equalToIgnoringCase, equalToIgnoringWhiteSpace
  • containsString, endsWith, startsWith
Loading script...

Related Posts

codechachaCopyright ©2019 codechacha