자바 Static, Kotlin Companion, 그리고 Annotation의 Function

DoDoBest

·

2024. 3. 16. 00:00

아직 작성 중인 미완성 글입니다.

 

Annotation 파라미터 특징

애노테이션 인자는 컴파일 시점에 그 값이 무엇인지 알 수 있어야 합니다. 따라서 임의의 프로퍼티를 인자로 지정할 수는 없습니다. 프로퍼티를 애노테이션 인자로 사용하려면 그 앞에 const 변경자(컴파일러는 const가 붙은 프로퍼티를 컴파일 시점 상수로 취급합니다.)를 붙여야 합니다.

    const val TEST_TIMEOUT = 100L
    
    @Test(timeout = TEST_TIMEOUT) fun testMethod() { ... }

함수는 상수가 아닌 걸까요?

JUnit5 테스트 코드를 작성할 때, ParameterizedTest에서 MethodSource의 인자로 전달하는 함수에 JvmStatic 애노테이션을 붙이지 않으면 테스트 코드가 동작하지 않습니다.

함수 코드는 컴파일 시점에 분명하게 알 수 있는데, JvmStatic을 붙이지 않으면 에러가 발생하는 이유는 무엇일까요? 함수는 상수가 아닌걸까요?

    @ParameterizedTest
    @MethodSource("createInputAboutBracketsWithAnswer")
    @DisplayName("정상적인 괄호 연산식을 입력하면 연산 결과를 변환한다")
    fun `test for input with brackets`(data: Pair<String, Double>) {
        // given
        val (input, expected) = data

        // when
        val actual = calculator.calculate(input)

        // then
        assertThat(actual).isEqualTo(expected)
    }

    companion object {
        @JvmStatic // 이 애노테이션을 붙이지 않으면 에러 발생
        fun createInputAboutBracketsWithAnswer(): List<Pair<String, Double>> {
            return listOf(
                "(1.4)" to 1.4,
                "(1)" to 1.0,
            )
        }
    }

 

 

MethodSource 파라미터 조건

JUnit5 공식 문서에서 MethodSource 애노테이션은 test class 또는 external class의 factory method를 참조할 수 있게 해준다고 설명되어 있습니다.

@MethodSource allows you to refer to one or more factory methods of the test class or external classes.

 

또한 Test 클래스 앞에 @TestInstance(Lifecycle.PER_CLASS) 애노테이션을 붙이지 않는한, factory method는 static이어야 하며, external 클래스의 factory method는 반드시 항상 static 이어야 한다고 명시되어 있습니다.

 

https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-sources-MethodSource

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo

junit.org

 

상수의 정의

Constant 상수는 Stack, Heap 메모리 영역에 올린 후 실행해서 해석하는 과정 없이 기계어 자체만으로 의미를 알 수 있는 것을 말합니다.

 

예를 들어 아래와 같은 상수는 기계어로 보든, 데이터 영역에 로드하든 1이라는 것을 바로 알 수 있습니다.

private const val NUM = 1

 

반면 아래와 같은 함수는 기계어 글자 만으로는 어떤 의미인지 알 수 없습니다.

우리가 눈을 통해 코드 한 줄 한 줄 읽어가며 listOf("a", "bc")를 반환한다는 것을 해석하는 것처럼, 컴파일러도 기계어를 메모리에 올려 어떤 값을 반환하는지 해석하는 과정이 필요합니다.

fun createData(): List<String> {
    val returnValue = mutableListOf<String>()
    returnValue.add("a")

    val sb= StringBuilder()
    sb.append("b")
    sb.append("c")
    returnValue.add(sb.toString())
    return returnValue
}

 

아래와 같이 외부 서버로부터 값을 받아오는 함수는 실행을 하고나서야 리턴 값을 알 수 있다는 사실이 더욱 분명합니다.

fun fetchUserInfo(): String {
    val userInfo = userRepository.getUserInfo()
    
    return userInfo.name
}

 

오해한 개념 : First class != 상수

 

Kotlin은 일급 함수(first class) 특징을 가집니다. 함수를 변수에 저장할 수 있고, 파라미터로 전달할 수 있으며, 함수의 리턴 값으로 함수를 반환할 수도 있습니다.

 

변수처럼 다룰 수 있다 + 상수처럼 개발자가 작성한 코드는 런타임에 바뀌지 않는다

 

두 특징을 종합하여 함수는 상수라고 생각했습니다.

이전 상수의 정의를 통해 함수는 상수가 아닌 이유를 알아봤고, 일급 함수는 단지 특징의 하나일 뿐 상수의 정의를 만족하지 않음을 알 수 있습니다.

 

애노테이션에 함수 직접 넣어보기

(애토네이션에 함수를 넣을 수 없는 이유 GPT 답변 참고해서 정리하기)

자바 애노테이션 인터페이스에서는 함수를 선언할 수 있는데, 안 되는 이유 찾아봐야함

 

 

method를 입력받는 애노테이션 직접 만들기

MethodSource의 파라미터는 왜 꼭 static 이어야 하는 걸까요? 직접 method를 입력받는 애노테이션을 만들어 보면서 확인해보겠습니다.

 

MethodSource는 java로 작성되어 있으며, 다음과 같이 구현되어 있습니다. 

 

Kotlin에서는 public @interface가 아닌 annotation class로 선언합니다.

 

@Target(AnnotationTarget.FUNCTION)
annotation class CustomAnnotation

 

자바로 디컴파일된 코드를 확인해보면 다음과 같습니다. 

 

Retention은 정의한 애노테이션 클래스를 소스 수준에서만 유지할지, .class 파일에 저장할지, 실행 시점에 리플렉션을 사용해 접근할 수 있게 할지를 지정하는 메타애노테이션입니다. 코틀린에서는 명시하지 않으면 기본적으로 RUNTIME으로 설정합니다.