맞는데 왜 틀릴까..?

Java/Effective Java

[Effective Java] Item 39. 명명 패턴보다 애너테이션을 사용하라

안도일 2023. 6. 30. 19:16

 

명명 패턴의 단점

 

테스트 메서드 이름을 test로 시작했을 때 단점 (명명 패턴)

 

  1. 오타 - 실수로 tsetSafetyOverride로 지으면 테스트 프레임워크가 무시하고 지나친다
  2. 올바른 프로그램 요소에서만 사용되리라 보증할 방법이 없다. (클래스 이름을 TestSafetyMechanisms로 지었을 때 Junit은 클래스 이름에 관심이 없다.)
  3. 프로그램 요소를 매개변수로 전달할 방법이 없다. (특정 예외를 던져야만 성공하는 테스트가 있을 때, 기대하는 예외 타입을 테스트에 전달해야 한다.)

 

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로 반복 가능 애너테이션이 달렸는지 검사하면 그렇지 않다라고 알려주고, 컨테이너 애너테이션이 달렸는지 검사한다면 반복 가능 애너테이션을 한 번만 단 메서드를 무시하고 지나친다.
  • 결론 : 모두 검사하려면 둘을 따로따로 확인해야 한다.

 

 

결론

 

  • 애너테이션으로 할 수 있는 일을 명명 패턴으로 처리할 이유는 없다