Unit Test에서 AssertThat을 사용하자

Junit 4.4부터 assertThat 메서드가 추가됐다. 이 메서드는 hamcrest 라이브러리의 사용을 통합하며 assertions을 작성하는데 있어 더 나은 방법을 제공한다. hamcrest가 static 메서드로 제공하는 여러 matcher를 사용할 수 있고 이러한 static 메서드는 체이닝할 수 있어서 기존 assertXXX 메서드보다 더 많은 유연성을 제공한다. 그 외에도 assertThat을 사용했을 때 어떤 이점이 있는지 알아보자.

해당 포스팅에서 사용한 코드는 https://github.com/jongmin92/code-examples/tree/master/java/assert-that 에서 확인할 수 있습니다.

가독성

assertThat이 assertXXX를 사용할 때 보다 더 읽기 쉽다.
두 값(객체)을 비교할 때 주로 사용하는 assertEquals를 먼저 살펴보자.

1
assertEquals(expected, actual);

assertEquals를 사용할 때 마다 expected와 actual의 위치에 대해서 헷갈릴 때가 많다. assertThat을 사용해서 작성하면 그 의미를 더 분명히 할 수 있다.

1
2
3
assertThat(actual, is(equalTo(expected)));
// is(equalTo)는 is로 대체할 수 있다.
assertThat(actual, is(expected));

assertThat을 사용하면 expected와 actual이 들어갈 위치가 조금 더 명확히 보인다. “actual이 expected와 같다(= 실제 값이 예상하는 값과 같다)” 라는 식으로 문장으로 읽히기도 한다.

하나의 예를 더 살펴보자. assertNotEquals가 없기 때문에 두 변수에 대한 equals를 수행하고 assertFalse를 사용해 검증해야한다.

1
assertFalse(expected.equals(actual));

이를 assertThat으로 변경하면 의미가 더 분명하고 읽기 쉽게 사용할 수 있다.

1
2
3
asserThat(actual, is(not(equalTo(expected))));
// is(equalTo)는 is로 대체할 수 있다.
asserThat(actual, is(not(expected)));

Failure 메시지

assertThat은 더 나은 에러 메시지를 제공한다.

먼저 assertTrue를 사용하는 예와 실패 메시지를 살펴보자.

1
2
3
4
5
6
7
assertTrue(expected.contains(actual));

--- 실행 결과 ---
java.lang.AssertionError
at org.junit.Assert.fail(Assert.java:86)
at org.junit.Assert.assertTrue(Assert.java:41)
at org.junit.Assert.assertTrue(Assert.java:52)

특정 string을 포함하는지 확인하기 위해서는 assertStringContains와 같은 메서드가 없기 때문에 contains 메서드를 사용하고 assertTrue을 이용해 검증해야 한다. 여기서 문제는 assertion error는 expected 값과 actual 값에 대해 알려주지 않는다는 것이다.

assertThat을 사용하도록 변경해보자.

1
2
3
4
5
6
7
8
9
assertThat(actual, containsString(expected));

--- 실행 결과 ---
Expected: a string containing "expected"
but: was "actual"
java.lang.AssertionError:
Expected: a string containing "expected"
but: was "actual"
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)

expected 값과 actual 값 모두 에러 메시지에 반환된다. 원인을 찾기 위해 별도의 디버깅 필요없이 에러 메시지 만으로 잘못된 부분을 바로 파악할 수 있다.

Type 안정성

assertThat을 사용하면 Type에 대한 안정성도 얻을 수 있다.

1
assertEquals("abc", 123); // 컴파일에는 성공하지만, 실행시 실패한다.

assertEquals의 구현은 다음과 같다. (Object로 인자를 받고 있다.)

1
2
3
static public void assertEquals(Object expected, Object actual) {
assertEquals(null, expected, actual);
}

반면에 assertThat은 위와 같은 경우 컴파일을 허용하지 않는다.

1
assertThat(123, is("abc")); // 컴파일 실패

assertThat의 구현은 다음과 같다. (Generic을 사용하고 있어 Type이 체크된다.)

1
2
3
public static <T> void assertThat(T actual, Matcher<? super T> matcher) {
assertThat("", actual, matcher);
}

유연성

hamcrest는 anyOfallOf와 같은 논리 matcher도 제공한다.

1
assertThat("test", allOf(is("test2"), containsString("te")));

allOf matcher는 and 논리 연산자처럼 동작한다. 따라서 제공된 모든 matcher에 통과해야한다. 실패하는 경우 다음과 같이 실패한 matcher에 대해서 에러 메시지를 반환한다.

1
2
3
4
5
6
Expected: (a string containing "te" and is "test2")
but: is "test2" was "test"
java.lang.AssertionError:
Expected: (a string containing "te" and is "test2")
but: is "test2" was "test"
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)

Custom Matchers

custom assert 메서드를 만들 수 있는 것처럼 다음과 같이 custom matcher를 생성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

public static class CustomMatcher extends TypeSafeMatcher<String> {
private final String expected;

public CustomMatcher(String expected) {
this.expected = expected;
}

@Override
protected boolean matchesSafely(String item) {
return expected.equals(item);
}

@Override
public void describeTo(Description description) {
description.appendValue(expected);
}
}

Reference

assert 메서드와 그에 상응하는 assertThat(hamcrest 1.3 기준) matcher에 대해서 알아보자.

assertThat은 org.junit.Assert.assertThat를 static import해서 사용한다.

assert method assertThat static Import
assertEquals(“expected”, “actual”); assertThat(“actual”, is(“expected”)); org.hamcrest.core.Is.is
assertArrayEquals(new String[]{“test1”, “test2”}, new String[]{“test3”, “test4”}); assertThat(new String[]{“test1”, “test2”}, is(new String[]{“test3”, “test4”})); org.hamcrest.core.Is.is
assertTrue(value); assertThat(actual, is(true)); org.hamcrest.core.Is.is
assertFalse(value); assertThat(actual, is(false)); org.hamcrest.core.Is.is
assertNull(value); assertThat(actual, nullValue); org.hamcrest.core.IsNull.nullValue
assertNotNull(value); assertThat(actual, notNullValue); org.hamcrest.core.IsNull.notNullValue
assertSame(expected, actual); assertThat(actual, sameInstance(expected)); org.hamcrest.core.IsSame.sameInstance
assertNotSame(expected, actual); assertThat(actual, not(sameInstance(expected))); org.hamcrest.core.IsNot.not, org.hamcrest.core.IsSame.sameInstance
assertTrue(1 > 3); assertThat(1, greaterThan(3)); org.hamcrest.number.OrderingComparison.greaterThan
assertTrue(“abc”.contains(“d”)); assertThat(“abc”, containsString(“d”)); org.hamcrest.core.StringContains.containsString

이 외에도 더 많은 matcher가 있다.

  • org.hamcrest.beans
    • HasProperty
    • HasPropertyWithValue
    • SamePropertyValuesAs
  • org.hamcrest.collection
    • IsArray
    • IsArrayContaining
    • IsArrayContainingInAnyOrder
    • IsArrayContainingInOrder
    • IsArrayContainingWithSize
    • IsCollectionWithSize
    • IsEmptyCollection
    • IsEmptyIterable
    • IsIn
    • IsIterableContainingInAnyOrder
    • IsIterableContainingInOrder
    • IsIterableWithSize
    • IsMapContaining
  • org.hamcrest.number
    • BigDecimalCloseTo
    • IsCloseTo
    • OrderingComparison
  • org.hamcrest.object
    • HasToString
    • IsCompatibleType
    • IsEventFrom
  • org.hamcrest.text
    • IsEmptyString
    • IsEqualIgnoringCase
    • IsEqualIgnoringWhiteSpace
    • StringContainsInOrder

Unit Test에서 AssertThat을 사용하자

https://jongmin92.github.io/2020/03/31/Java/use-assertthat/

Author

KimJongMin

Posted on

2020-03-31

Updated on

2021-03-22

Licensed under

댓글