전통적으로 도구나 프레임워크나 특별히 다뤄야 할 프로그램 요소에는 딱 구분되는 명명 패턴을 적용해왔다.
추가로, 컴파일러는 문자열이 무엇을 가리키는지 알 방법이 없다. 애너테이션은 이 모든 문제를 해결해준다. JUnit도 버전 4부터 전면 도입하였다.
/**
* 테스트 메서드임을 선언하는 애너테이션이다.
* 매개변수 없는 정적 메서드 전용이다.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
public class Sample {
@Test
public static void m1() { // 설공해야 한다.
}
public static void m2() {
}
@Test
public static void m3() { // 실패해야 한다.
throw new RuntimeException("실패");
}
public static void m4() {
}
@Test
public void m5() { // 잘못 사용한 예: 정적 메서드가 아니다.
}
public static void m6() {
}
@Test
public static void m7() { // 실패해야 한다.
throw new RuntimeException("실패");
}
public static void m8() {
}
}
public class RunTests {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException {
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 exception) {
System.out.println("잘못 사용한 @Test: " + m);
}
}
}
System.out.printf("성공: %d, 실패: %d%n", passed, tests - passed);
}
}
/**
* 명시한 예외를 던저야만 성공하는 테스트 메서드 애너테이션
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Throwable> value();
}
Class<? extend Throwable>
이다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Throwable>[] value();
}
public class RunTests {
@ExceptionTest({IndexOutOfBoundsException.class, NullPointerException.class})
public static void doublyBad() { // 성공해야 한다.
List<String> list = new ArrayList<>();
// 자바 API 명세에 따르면 다음 메서드는 IndexOutOfBoundsException이나 NullPointerException을 던질 수 있다.
list.addAll(5, null);
}
}
주의사항
// 반복 가능한 애너테이션
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest {
Class<? extends Throwable> value();
}
// 컨테이너 애너테이션
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
ExceptionTest[] value();
}