개요
•
전통적으로 Junit과 같은 프레임워크가 다루는 프로그램 요소에는 정확히 구분되는 명명 패턴을 적용해왔다. 하지만, 명명 패턴이 가지고 있는 대부분의 문제들을 애너테이션으로 해결할 수 있다.
명명 패턴의 문제
•
오타가 나면 안된다.
•
올바른 (기존에 프레임워크가 의도한) 프로그램 요소에서만 사용되리라 보증할 수 없다.
•
프로그램 요소를 매개변수로 전달할 방법이 없다.
개념 및 용어
•
meta annotation
◦
애너테이션 선언에 다는 애너테이션
•
marker annotation
◦
매개변수 없이 단순히 target이 되는 프로그램 요소에 marking하는 애너테이션
예제코드
@Retention(RetentionPolicy.RUNTIME) // meta annotation (보존 정책)
@Target(ElementType.METHOD) // meta annotation (적용 대상)
public @interface Test { // marking annotation
}
JavaScript
복사
•
Test 애너테이션을 정의하였다.
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) { // @Test 애너테이션을 잘못된 곳에 사용해서 발생한 예외(기대했던 예외와 다른)
System.out.println("잘못 사용한 @Test: " + m);
}
}
}
System.out.printf("성공: %d, 실패 : %d%n", passed, tests - passed);
}
}
JavaScript
복사
•
위의 Runtests는 명령줄 인수로 클래스 이름을 받아서 @Test 애너테이션이 달린 메서드를 차례대로 호출한다.
// 명시한 예외가 발생해야 성공하는 테스트 메서드용 애너테이션
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Throwable> value();
}
JavaScript
복사
•
ExceptionTest 애너테이션의 매개변수 타입은 Class<? extends Throwable>로, Throwable을 확장한 클래스의 Class 객체라는 의미이다. 따라서, 모든 예외와 오류 타입을 수용한다. (모든 예외와 오류 객체들은 Throwable 클래스를 상속하기 때문)
public class Sample2 {
@ExceptionTest(ArithmethicException.class)
public static void m1() { // 성공
int i = 0;
i = i / i;
}
@ExceptionTest(ArithmethicException.class)
public static void m2() { // 실패 (다른 예외 발생)
int[] a = new int[0];
int i = a[1];
}
@ExceptionTest(ArithmethicException.class)
public static void m3() { } // 실패 (예외 발생 안함)
}
JavaScript
복사
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(ExceptionTest.class)) {
tests++;
try {
m.invoke(null);
System.out.println("테스트 실패: 예외 발생 안함");
} catch (InvocationTargetException wrappedExc) {
Throwable exc = wrappedExc.getCause();
Class<? extends Throwable> excType = m.getAnnotation(ExceptionTest.class).value();
if (excType.isInstance(exc)) {
passed++;
} else {
System.out.println("테스트 실패: 기대한 예외와 다른 예외 발생");
}
} catch (Exception exc) {
System.out.println("잘못 사용한 경우");
}
}
}
System.out.printf("성공: %d, 실패 : %d%n", passed, tests - passed);
}
}
JavaScript
복사
•
@Test 애너테이션과 비슷하게 동작하지만, 애너테이션 매개변수의 값을 추출하여 테스트 메서드가 기대한 예외를 던지는지 확인한다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Throwable>[] value();
}
JavaScript
복사
•
위와 같이, 여러 개의 예외를 매개변수를 배열로 받는 애너테이션을 만들수도 있다.
•
위의 방식을 Repeatable 메타 애너테이션으로도 구현할 수 있다.
◦
Repeatable 메타 애너테이션이 붙은 애너테이션은 하나의 프로그램 요소에 여러 번 달 수 있다.
내용
•
애너테이션은 애너테이션을 다는 프로그램 요소(클래스, 메서드 등)의 의미는 그대로 두고, 그 애너테이션에 관심있는 프로그램(ex. Junit과 같은 프레임워크)에게 추가 정보를 제공한다.
•
정의하고자 하는 애너테이션이 특정 프로그램 요소에만 쓰이도록 하고 싶다면 (ex. 정적 메서드 전용) 이러한 기능을 하는 애너테이션 처리기를 구현해야 한다.
◦
javax.annotation.processing API (Reference)
결론
•
애너테이션으로 할 수 있는 일을 명명패턴으로 처리할 이유는 단 한가지도 없다.