예를 들면 JUnit3 에서는 테스트 메서드 이름을 test로 시작하게끔 했다.
이러한 명명 패턴 방식은 효과적이지만 단점도 크다.
test로 시작해야하는데 실수로 이름을 tsetSafetyOverride로 지으면 테스트 메서드로 인식하지 못하고 테스트를 수행하지 않는다.
JUnit3의 명명 패턴인 'test'를 메서드가 아닌 클래스의 이름으로 지음으로써 해당 클래스의 모든 테스트 메서드가 수행되길 바랄 수 있지만 JUnit은 클래스 이름에는 관심이 없다. 따라서 개발자가 의도한 테스트는 전혀 수행되지 않는다.
특정 예외를 던져야 성공하는 테스트가 있을 때, 메서드 이름에 포함된 문자열로 예외를 알려주는 방법이 있지만 보기 흉할 뿐 아니라 컴파일러가 문자열이 예외 이름인지 알 도리가 없다.
애너테이션을 사용하면 위의 단점을 모두 해결할 수 있다.
/**
* @Test
* 테스트 메서드임을 선언하는 애너테이션이다.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test{}
@Retention(RetentionPolicy.RUNTIME) - 보존 정책
@Target(ElementType.METHOD) - 적용 대상
// 마커 애너테이션 처리기
public class RunTests {
public static void main(String[] args) throws Exception {
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName(args[0]);
for (Method m : testClass.getDeclaredMethods()) {
if (m.isAnnotationPresent(Test.class)) {
tests++;
try {
m.invoke(null);
passed++;
} catch (InvocationTargetException wrappedExc) {
Throwable exc = wrappedExc.getCause();
System.out.println(m + " 실패: " + exc);
} catch (Exception exc) {
System.out.println("잘못 사용한 @Test: " + m);
}
}
}
System.out.printf("성공: %d, 실패: %d%n",
passed, tests - passed);
}
}
리플렉션을 이용하여 마커 애너테이션을 찾고, 예외 발생 시 InvocationTargetException으로 Wrapping 되어 해당 예외에 담긴 실패 정보를 추출해서 출력한다.
애너테이션으로 처리할 수 있다면 명명 패턴을 사용할 이유는 없다.
자바 프로그래머라면 자바가 제공하는 애너테이션 타입들은 사용하자!
@Override 애너테이션은 상위 타입의 메서드를 재정의했음을 나타낸다.
메서드 선언에만 달 수 있으며, 일관되게 사용하면 여러 가지 버그들을 예방할 수 있다.
@Override 애너테이션을 달아줌으로써 '재정의한다.'라는 의도를 명확히 나타낼 수 있다.
또한, 코드를 잘못 작성(Overriding이 아닌 Overloading)했을 경우 컴파일러가 잘못된 부분을 명확히 알려준다.
구체 클래스에서 상위 클래스의 추상 메서드를 재정의할 때는 @Override를 달지 않아도 된다.
구체 클래스인데 아직 구현하지 않은 추상 메서드가 있다면 컴파일러가 이를 알려주기 때문이다.
Java8에서 디폴트 메서드를 지원하기 시작하면서, 인터페이스의 추상 메서드를 구현한 메서드에도 @Override를 붙여주는 게 좋다. 이렇게 하면 메서드의 시그니처가 올바른지 재차 확신할 수 있기 때문이다.
또한, 실수로 추가한 메서드가 있는지 확인하기 위해 추상 클래스나 인터페이스에서는 상위 클래스나 상위 인터페이스의 메서드를 재정의하는 모든 메서드에 @Override를 붙여주는 게 좋다.
상위 클래스의 메서드를 재정의하려는 모든 메서드에 @Override 애너테이션을 달자.