명명 패턴의 단점
테스트 메서드 이름을 test로 시작했을 때 단점 (명명 패턴)
- 오타 - 실수로 tsetSafetyOverride로 지으면 테스트 프레임워크가 무시하고 지나친다
- 올바른 프로그램 요소에서만 사용되리라 보증할 방법이 없다. (클래스 이름을 TestSafetyMechanisms로 지었을 때 Junit은 클래스 이름에 관심이 없다.)
- 프로그램 요소를 매개변수로 전달할 방법이 없다. (특정 예외를 던져야만 성공하는 테스트가 있을 때, 기대하는 예외 타입을 테스트에 전달해야 한다.)
Test 애너테이션
메타애너테이션
애너테이션 선언에 다는 애너테이션이다.
Ex)
@Retention(RetentionPolicy.RUNTIME) : @Test가 런타임에도 유지되어야 한다는 표시
@Target(ElementType.METHOD) : @Test가 반드시 메서드 선언에서만 사용돼야 한다고 알려준다 (클래스, 필드 등 다른 요소에 달 수 없다.)
마커애너테이션 (아무 매개변수 없이 단순히 대상에 마킹한다는 뜻)
마커 애너테이션을 사용한 프로그램
- m3와 m7 메서드는 예외를 던지고 m1과 m5는 그렇지 않다.
- m5는 정적 메서드가 아닌 인스턴스 메서드이므로 @Test를 잘못 사용한 경우다.
- @Test를 붙이지 않은 나머지 4개의 메서드는 테스트 도구가 무시할 것이다.
마커 애너테이션을 처리하는 프로그램
- Sample 클래스에서 @Test 애너테이션이 달린 메서드를 차례로 호출한다.
- isAnnotationPresent가 실행할 메서드를 찾아주는 메서드다.
- 테스트 메서드가 예외를 던지면 InvocationTargetException으로 감싸서 다시 던지고, 해당 프로그램이 이 예외를 잡아 원래 예외에 담긴 실패 정보를 추출해(getCause) 출력한다.
- InvocationTargetException 외의 예외가 발생한다면 @Test 애너테이션을 잘못 사용했다는 뜻이다. 두번째 catch 블록이 이러한 예외를 잡아 오류 메시지를 출력한다.
실행 결과
매개변수를 받는 애너테이션 타입
해당 애너테이션의 매개변수 타입 Class<? extends Throwable>은 Throwable을 확장한 클래스의 Class 객체라는 뜻이며, 따라서 모든 예외, 오류 타입을 수용한다는 의미다.
매개변수 하나짜리 애너테이션을 사용한 프로그램
매개변수 하나짜리 애너테이션을 처리하는 프로그램
- @Test 애너테이션용 코드와 비슷하지만 해당 코드는 애너테이션 매개변수의 값을 추출하여 테스트 메서드가 올바른 예외를 던지는지 확인하는데 사용한다.
실행 결과
배열 매개변수를 받는 애너테이션 타입
예외를 여러 개 명시하고 그중 하나가 발생하면 성공하게 만들어 보자
이런 경우에는 @ExceptionTest 애너테이션의 매개변수 타입을 Class 객체의 배열로 수정하면 된다.
배열 매개변수를 받는 애너테이션을 사용한 프로그램
- 원소가 여럿인 배열을 지정할 때는 원소들을 중괄호로 감싸고 쉼표로 구분해주기만 하면 된다.
- 앞서 작성한 @ExceptionTest도 모두 수정 없이 수용한다.
배열 매개변수를 받는 애너테이션을 처리하는 프로그램
실행 결과
@Repeatable 애너테이션
자바 8부터 여러 개의 값을 받는 애너테이션을 배열 매개변수를 사용하는 대신 애너테이션에 @Repeatable 메타애너테이션을 다는 방식이 생겼다.
- @Repeatable을 단 애너테이션은 하나의 프로그램 요소에 여러 번 달 수 있다.
- @Repeatable을 단 애너테이션을 반환하는 '컨테이너 애너테이션'을 하나 더 정의하고, @Repeatable에 이 컨테이너 애너테이션의 class 객체를 매개변수로 전달해야 한다.
- 컨테이너 애너테이션은 내부 애너테이션 타입의 배열을 반환하는 value 메서드를 정의해야 한다.
- 컨테이너 애너테이션 타입에는 적절한 보존 정책(@Retention)과 적용 대상(@Target)을 명시해야 한다. 그렇지 않으면 컴파일 되지 않는다.
컨테이너 애너테이션 타입
반복 가능한 애너테이션 타입
반복 가능 애너테이션을 두 번 단 코드
반복 가능 애너테이션을 처리하는 프로그램
반복 가능 애너테이션을 여러 개 달면 하나만 달았을 때와 구분하기 위해 해당 컨테이너 애너테이션 타입이 적용된다.
- getAnnotationsByType 메서드는 이 둘을 구분하지 않아서 반복 가능 애너테이션과 그 컨테이너 애너테이션을 모두 가져오지만, isAnnotationPresent 메서드는 둘을 명확히 구분한다.
- 따라서 반복 가능 애너테이션을 여러 번 단 다음 isAnnotationPresent로 반복 가능 애너테이션이 달렸는지 검사하면 그렇지 않다라고 알려주고, 컨테이너 애너테이션이 달렸는지 검사한다면 반복 가능 애너테이션을 한 번만 단 메서드를 무시하고 지나친다.
- 결론 : 모두 검사하려면 둘을 따로따로 확인해야 한다.
결론
- 애너테이션으로 할 수 있는 일을 명명 패턴으로 처리할 이유는 없다
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] Item 41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라 (0) | 2023.06.30 |
---|---|
[Effective Java] Item 40. @Override 애너테이션을 일관되게 사용하라 (0) | 2023.06.30 |
[Effective Java] Item 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라 (0) | 2023.06.29 |
[Effective Java] Item 37. ordinal 인덱싱 대신 EnumMap을 사용하라 (0) | 2023.06.29 |
[Effective Java] Item 36. 비트 필드 대신 EnumSet을 사용하라 (0) | 2023.05.16 |