Item39. 명명 패턴보다 애너테이션을 사용하라!
명명패턴
- 전통적으로 도구나 프레임워크가 특별히 다뤄야 할 프로그램 요소에는 딱 구분되는 명명 패턴을 적용해왔다.
명명패턴의 단점
- 오타가 나면 안된다.
- JUnit3 버전까지 테스트 메서드를 test로 시작하게끔 명명했는데, 실수로 test를 잘못 적으면 테스트를 건너뛴다.
- 올바른 프로그램 요소에서만 사용되리라 보증할 방법이 없다.
- test 명명은 메서드에 적용해야 하는데, 클래스에 이러한 이름 명명을 적용해놓고 테스트가 진행되기를 기대할 수 있다.
- 실제로 테스트 안된다.
- test 명명은 메서드에 적용해야 하는데, 클래스에 이러한 이름 명명을 적용해놓고 테스트가 진행되기를 기대할 수 있다.
- 프로그램 요소를 매개변수로 전달할 마땅한 방법이 없다.
- 특정 예외를 던져야만 성공하는 케이스가 있을 때, 기대하는 예외 타입을 테스트에 매개변수로 전달해야 하는 상황이다.
- 명명 패턴으로 이를 전달하는 데에는 무리가 있다.
- 특정 예외를 던져야만 성공하는 케이스가 있을 때, 기대하는 예외 타입을 테스트에 매개변수로 전달해야 하는 상황이다.
애너테이션
- 명명 패턴의 모든 문제를 해결해주는 멋진 개념이다.
- JUnit도 버전 4부터 도입했다.
선언
애너테이션 선언에 다는 애너테이션인 메타애너테이션이 필요하다.
@Retention : @Test가 런타임에도 유지되어야 한다는 표시
@Target : @Test가 반드시 메서드 선언에서만 사용돼야 한다고 알려줌.
@Test 애너테이션 적용 예
1
2
3
4
5
6
7
public class Sample {
@Test public static void m1(){} // 성공
public static void m2(){} // 수행 안 됨.
@Test public static void m3() {throw new RuntimeException("fail");} // 실패 - 약속 안 된 예외 던지기
}
- @Test 애너테이션이 Sample 클래스의 의미에 직접적인 영향을 주지는 않는다.
- 그저 이 애너테이션에 관심 있는 프로그램에게 추가 정보를 제공한다. (특별한 처리의 기회를 준다.)
마커 애너테이션을 처리하는 프로그램
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 e) {
System.out.println("잘못 사용한 @Test: " + m);
}
}
}
System.out.printf("성공: %d, 실패: %d%n", passed, tests - passed);
}
}
- if (m.isAnnotationPresent(Test.class)) : 테스트 애너테이션이 선언된 메서드만 호출한다.
- 테스트 메서드가 예외를 던지면 리플렉션 매커니즘이 InvocationTargetException로 다시 감싸서 던진다.
- InvocationTargetException를 잡아 원래 예외에 담긴 정보를 출력한다.
- InvocationTargetException 외의 예외 발생 시, @Test 애너테이션을 잘못 사용한 것이다.
- 인스턴스 메서드, 매개변수가 있는 메서드, 호출할 수 없는 메서드 등에 단 경우
특정 예외를 던져야만 성공하는 예제
1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Throwable> value();
}
- 매개변수 하나를 받는 애너테이션 타입 예제다.
- 매개변수의 타입은 Class<? extends Throwable>이다. Throwable을 확장한 클래스의 Class 객체라는 의미다.
- 모든 예외와 오류 타입을 다 수용한다.
@ExceptionTest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Sample2 {
@ExceptionTest(ArithmeticException.class)
public static void m1() { // 성공
int i = 1 / 0; // ArithmeticException를 던진다.
}
@ExceptionTest(ArithmeticException.class)
public static void m2() { // 실패(다른 예외)
int[] arr = new int[0];
arr[1] = 1; // IndexOutOfRange
}
@ExceptionTest(ArithmeticException.class)
public static void m3() {} // 실패(에외 발생 안함)
}
- @ExceptionTest를 사용하는 예제다.
결론
- 애너테이션 사용을 통해 코드의 가독성 개선을 보일 수 있다.
- 그 과정에서 애너테이션을 선언하고 이를 처리하는 부분은 코드 양이 늘어난다. 특히 처리 코드의 경우 복잡해져 오류가 날 수 있다.
- 애너테이션으로 할 수 있는 일을 명명 패턴으로 처리할 이유는 없다.
- 자바 프로그래머라면 예외 없이 자바가 제공하는 애너테이션 타입들은 사용해야 한다.
- 도구 제작자를 제외하고는, 일반 프로그래며가 애너테이션 타입을 직접 정의할 일은 거의 없다.
Comments powered by Disqus.