public class HamcrestTest {
@Test
void is_IntAndLongGiven_True() {
assertThat(0 == 0L, is(true)); // Success
assertThat(0, is(0L)); // Failure
assertThat(0L, is(0)); // Failure
}
}
Expected: <0L>
but: was <0>
위 테스트 코드가 실패했다.
사소한 문제긴 하지만, 0 == 0L
이 true인 반면 0 is 0L
이 false인게 납득되지 않았다.
정확한 이유가 궁금해져서 작은 삽질을 해봤다.
첫 번째로, is()
메소드는 equalTo()
메소드와 같은 역할을 수행한다.
따라서 이 문제는 equalTo()
메소드를 사용했을 경우와 동일하게 발생한다. 정확히는 equalTo()에 의해 발생하는 문제가 된다.
두 번째로, equalTo()
메소드는 검사할 객체의 equalTo()
메소드를 호출하는 방식으로 테스트를 진행한다.
그런데 나는 메소드가 없는 원시 타입을 인자로 줬다. 이 경우 equals()
메소드는 어떻게 호출하는 걸까?
생각해보면 애초에 equalTo()
는 원시 타입을 받을 수 없는 메소드다. Generic 타입의 인자를 받는 메소드이기 때문이다.
그럼 Generic 타입의 인자를 받는 메소드에 원시 타입은 어떻게 전달될까?
Java는 원시 타입을 Generic 타입에 사용하기 위해 Autoboxing과 Unboxing을 활용한다.
즉, equalTo()
메소드의 인자에 0L을 넘겨주면 컴파일 시점에 Long.valueOf(0L)
로 변환되어 작동한다.
equalTo()
는 내부적으로 equals()
메소드를 실행하므로, 다음과 같은 코드가 실행되는 것이다:
Integer.valueOf(0).equals(Long.valueOf(0L))
이때 서로 다른 클래스의 인스턴스끼리 equals()
비교를 진행하면 true
가 나올 수 있을까?
The result is true if and only if the argument is not null and is an Integer object that contains the same int value as this object.
어림도 없지!
public class HamcrestTest {
@Test
void is_IntAndLongGiven_True() {
assertThat(0, equalTo(0));
assertThat(0, comparesEqualTo(0));
}
}
해답은 간단하게 서로 다른 형식의 원시 타입을 비교하지 않으면 된다.
다만 equalTo()
를 사용하면 그러한 실수를 컴파일 시점에 확인할 수 없다.
이때 comparesEqualTo()
를 활용하면 서로 비교할 수 있는 값끼리만 테스트할 수 있도록 만들어준다.
이렇게 컴파일 시점에 확인할 수 있다!
comparesEqualTo()
는 <T extends Compareable<T>>
형식의 Generic 인자를 받는 메소드이기 때문이다.
실수를 미연에 방지하고 싶다면 이 메소드를 사용하는 것도 나쁘지 않을 것 같다.
Java에서 원시 타입을 Generic 타입으로 전달할 때 Autoboxing이 일어난다.
서로 다른 클래스 인스턴스 간의 equals()
비교는 false
를 반환하므로 주의해야 한다.