ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 13. 자바 어노테이션을 만들고 task와 함께 사용하자
    스프링개발자/201 - 일반 2020. 7. 23. 09:13

    [배경]

    자바로 코딩하다보면, 다음과 같은 어노테이션을 많이 본다

    @SpringBootApplication
    @Override
    @Controller @RestController @Service @Configuration @Getter

    어노테이션은 블랙박스와 같다. 그 안에 뭐가 들어 있는지, 어떻게 행동하는지 분명하진 않지만,

    그냥 추가만 하면 마법처럼 작동한다. 

    이 글에서는 어노테이션의 정의를 알아보며 직접 만들어보고, 어떻게 실제 코드에 사용할지 생각해보자.

     

    어노테이션 역사가 궁금하다면 해당 문서 참고

    https://en.wikipedia.org/wiki/Java_annotation#History


    1. 어노테이션이 뭐지?

    아래 그림의 왼쪽 window 6번줄을 보면 @SpringBootApplication 어노테이션이 있다. 이렇게 생겼다.

    오른쪽 그림은 해당 어노테이션을 CTRL+클릭 했을때 나오는 자세한 설명이다. 어노테이션을 눌러서 어떻게 동작하는지, 어떤 정보들이 있는지 간단히 볼 수 있다.

     

    어노테이션을 어디에 붙일까? 클래스에 붙이기도하고, 인스턴스에 붙이기도 하던데?

    java.lang.annotation.ElementType에 보면 전부 볼 수 있다

    public enum ElementType:
    	
        /** Class, interface (including annotation type), or enum declaration */
        TYPE,
        
        /** Field declaration (includes enum constants) */
        FIELD,
        METHOD,
        PARAMETER,
        CONSTRUCTOR,
        LOCAL_VARIABLE,
        ANNOTATION_TYPE,
        PACKAGE,
        TYPE_PARAMETER,
        TYPE_USE,
        MODULE
    

    어노테이션은 언제 까지 효력을 발휘할까? java.lang.annotation.RetentionPolicy에 보면 다음 세가지가 나온다.

    public enum RetentionPolicy {
        /**
         * Annotations are to be discarded by the compiler.
         */
        SOURCE,
    
        /**
         * Annotations are to be recorded in the class file by the compiler
         * but need not be retained by the VM at run time.  This is the default
         * behavior.
         */
        CLASS,
    
        /**
         * Annotations are to be recorded in the class file by the compiler and
         * retained by the VM at run time, so they may be read reflectively.
         *
         * @see java.lang.reflect.AnnotatedElement
         */
        RUNTIME
    }

     

    어노테이션을 CTRL(CMD) + 클릭 하여 그 안에 정보(설명 및 패키지 이름)를 확인하는것은 좋은 습관인거 같다.

     

    어노테이션과 관련된 소스코드는 보이지 않지만, annotationProcessor가 살을 붙여서 실행해준다. 언제까지 어노테이션의 정보가 남아있는지는 RetentionPolicy enum을 보면 알 수 있다; Source, Class and RunTime

    1. RetentionPolicy.SOURCE: 컴파일러에 의해 무시되는 어노테이션
    2. RetentionPolicy.CLASS: 컴파일러에 의해 기록은 되지만, 런타임때 VM단에서는 필요 없다. 디폴트 값.
    3. RetentionPolicy.RUNTIME: 컴파일러에 의해 class 파일로 기록되며, VM에 의해서 런타임때에도 남아있는다

    2. 어노테이션 만들기

    source body가 없다는 측면에서, 어노테이션은 interface로 분류된다. interface 글씨앞에 @표시가 붙는거 주의요망. 테스트용 어노테이션 3가지 (even, odd, prime) 를 test package에 만들자

    package me.ndPrince.routeOptimization.annotation;
    
    import org.junit.jupiter.api.Tag;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Tag("odd")
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface odd {
    }

    Tag는 junit junipter에서 추가 된 어노테이션으로 테스트에 도움을 준다. even과 prime 어노테이션을 위와 동일한 원리로 만들자.

     

    이해를 돕기 위해, odd 타겟은 TYPE 그리고 even 타겟은 FIELD로 설정해보자

     

    Line7 and line10에 빨간줄을 볼 수 있다; ElementType 이 일치하지 않기 때문.

    타겟을 다시 TYPE(클래스 용) 으로 바꿔준다. 빨간줄이 사라졌다.

     

    3. 테스트 하기

    unit test 클래스 5개 만들고 어노테이션 적절히 붙이기

    package me.ndPrince.routeOptimization;
    
    import me.ndPrince.routeOptimization.annotation.even;
    import me.ndPrince.routeOptimization.annotation.prime;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.springframework.test.context.junit.jupiter.SpringExtension;
    
    @ExtendWith(SpringExtension.class)
    @even @prime
    class TestTwo {
    
        @Test
        void testTwo(){
            System.out.println("testing two");
        }
    }

    TestOne 부터 TestFive까지 다섯개의 test클래스를 만들고, 적절한 어노테이션을 붙였다.

    2라는 숫자는, 짝수이고, 소수 이기 때문에 TestTwo 클래스에 두개의 어노테이션이 붙고

    3이라는 숫자는 홀수에만 해당됨으로, TestThree 클래스에는 odd 어노테이션만 붙었다.

    sout 메세지를 다르게 해서, 어떤 클래스가 실행되는지 구분할 수 있다.

     

    build.gradle에 다음의 task를 추가한다 (task에 대해서는 이전 글(12) 참고)

    현재 useJUnitPlatform을 인식 못하고 있다. 

     

    시간이 꽤 지났는데, 이제 발견했다. gradle에 대해 좀 더 공부 해야 겠다.

    다음을 build.gradle에 추가하자;

    test {
    	useJUnitPlatform {
    		includeTags('even', 'odd', 'prime')
    	}
    }

    다음의 그레이들 문서에 따르면 JUnitPlatform을 사용하면 tag를 같이 사용할 수 있으며, 포함/제외할 태그도 명시 할 수 있다. 또한 위의 그림으로 박제 되어 있는 task를 수정하자

    task oddTest(type: Test){
    	group "demo test"
    	useJUnitPlatform{
    		includeTags 'odd'
    	}
    }

    type을 이용함으로, Test 클래스에 있는 useJUnitPlatform 메소드를 가져오도록 하였다.

     

    오른쪽 그레이들 창에 보면, demo test 그룹이 있고, 거기서 세개의 테스트를 모두 돌려보자.

    evenTest의 경우에 1개의 테스트가 실행되었다; Four; excludeTags의 영향때문에 Two가 제외되었다.

    oddTest의 경우 3개의 테스트가 실행되었다; One, Five, Three

    primeTest의 경우도 3개의 테스트가 실행되었다; Five, Three, Two

     

    소스코드 보기

    https://github.com/2ndPrince/routeOptimization/tree/spring-101-article-13


    [TroubleShoot]

    1. Jupiter api 의존성을 추가했음에도 다음의 에러 메세지가 떴다.

    그 이유는 어노테이션이 선언된 곳이 main인데 반해, 쥬피터 의존성은 testImplementation 을 쓰고 있기 때문이다.

    본 어노테이션은 테스트만을 위한 어노테이션이므로, src.test 아래의 경로로 이동해서 문제를 해결할수 있다.

    2. 그레이들 그룹 테스트에서 test events were not received 메세지와 함께 테스트가 실행되지 않는다면

    이전 테스트 했을때와 파일이 달라진게 없어서, 테스트를 한번 더 돌리지 않는것이다. 

    ./gradlew clean 이후에 다시 실행해보면 결과값이 다시 나온다;

     

    [Use Case]

    1. 웹페이지 테스트 (Selenium)등을 이용할때 테스트할 페이지의 성격에 따라 다른 어노테이션을 붙일 수 있다.

    (스모크 테스트, a/b 테스트, regression 테스트 등등 다른 성격의 테스트마다 다른 어노테이션을 할당할 수 있다)

    예를들어, 회원가입과 관련된 페이지만 테스트 하고 싶을때 전용 어노테이션을 하나 만들어 붙일 수 있다.

    나중에 자바로 web front end 테스트 하는것을 카테고리로 하나 만들예정이다.

Designed by Tistory.