본문 바로가기
개발 이야기/Springboot

JUnit 5에서 조건부 @Disabled 적용하기

by 농개 2025. 4. 20.
반응형

목차

    1. @Disabled

    @DisabledJUnit에서 제공하는 테스트를 비활성화하는 기능을 가진 어노테이션입니다.

    아래와 같이 사용 될 수 있습니다.

    @Disabled
    @Test
    fun test() {
    	Assertions.fail<String>("not executed")
    }

     

    Disabled로 설정한 이유를 기입 할 수도 있습니다.

    @Disabled("This is disabled because of a bug ticket BUG-12345")
    @Test
    fun test() {
    	Assertions.fail<String>("not executed")
    }

     

     

    2. 조건부로 Disabled 시켜야 하는 상황

    @Disabled를 사용하면 아래와 같은 시나리오로 사용 할 수 있습니다.

    • 특정 이슈로 인해 @Disabled 어노테이션을 붙여 테스트를 비활성화
    • 이슈로 해소되면 @Disabled를 제거하여 커밋

    위 같은 동작은 개발자가 직접 수작업 해줘야 하는 문제점이 있습니다.

    또한 두개의 상황이 반복적으로 발생 한다면 테스트 코드 관리에 어려움이 있을 수 있습니다.

     

    가령, 아래와 같은 이슈를 예시로 들어보겠습니다.

    WebClient를 통해 외부 API호출이 포함된 테스트 케이스

     

    사실 일반적이지는 않을 수 있습니다.

    왜냐하면 외부 의존성이 테스트 코드에 포함되는 것은 때에 따라 부적절 할 수 있습니다.

    여기서는 외부 API 호출이 가능하다면 테스트를 활성화하고, 불가능하다면 테스트를 비활성화 하는 샘플코드를 작성해 보겠습니다.

     

    아래와 같은 어노테이션도 있습니다.
    Os, SystemProperty, Env에 따라 테스트를 비활성 할 수 있습니다. 다만, 여기서는 다루지 않습니다.
    - @DisabledOnOs
    - @DisabledOnJre
    - @DisabledIfSystemProperty
    - @DisabledIfEnvironmentVariable

     

    3. ExecutionCodition을 구현한 클래스 작성

    class EnabledIfReachableCondition : ExecutionCondition {
    
        override fun evaluateExecutionCondition(context: ExtensionContext): ConditionEvaluationResult {
            val element = context.element.orElseThrow { IllegalStateException() }
            return findAnnotation(element, EnabledIfReachable::class.java)
                ?.let { disableIfUnreachable(it, element) }
                ?: enabled("@EnabledIfReachable is not present")
        }
    
        private fun disableIfUnreachable(
            annotation: EnabledIfReachable,
            element: AnnotatedElement
        ): ConditionEvaluationResult {
            val host = annotation.host
            val port = annotation.port
            val timeoutMillis = annotation.timeoutMillis
    
            return try {
                val reachable = pingHost(host, port, timeoutMillis)
                if (reachable)
                    enabled(format("%s is enabled because %s is reachable", element, host))
                else
                    disabled(
                        format(
                            "%s is disabled because %s could not be reached in %dms",
                            element, host, timeoutMillis
                        )
                    )
            } catch (e: Exception) {
                // 연결 시도 중 예외 발생 시 테스트를 disable 처리
                disabled(
                    format(
                        "%s is disabled because %s check failed with: %s",
                        element, host, e.message ?: "unknown error"
                    )
                )
            }
        }
    
        private fun pingHost(host: String, port: Int, timeout: Int): Boolean {
            try {
                Socket().use { socket ->
                    socket.connect(InetSocketAddress(host, port), timeout)
                    return true
                }
            } catch (e: IOException) {
                return false
            }
        }
    }

     

    ExecutionCondition 인터페이스를 구현한 새로운 클래스를 하나 만들어줬습니다.

    주요 함수는 어노테이션의 속성값을 읽어 Socket 통신이 가능한지 여부를 파악합니다.

    Socket 통신이 가능하다면 eval(enabled)

    불가능 하다면 eval(disabled) 방식로 동작하게 됩니다.

     

    4. 기능 확장을 위한 CustomAnnotation 생성

    아래와 같이 커스텀 어노테이션을 작성 합니다.

    @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
    @Retention(AnnotationRetention.RUNTIME)
    @ExtendWith(EnabledIfReachableCondition::class)
    annotation class EnabledIfReachable(
        val host: String,
        val port: Int,
        val timeoutMillis: Int
    )

     

     

    5. 테스트 코드 작성

    class WebClientUtilTest {
    
        @EnabledIfReachable(
            host = "localhost",
            port = 8080,
            timeoutMillis = 1000
        )
        @Test
        fun `test web client`() {
            val res = WebClientUtil
                .get("http://localhost:8080/api/hello", mapOf("Content-Type" to "text/plain"))
                .block()
    
    	Assertions.assertEquals("hello world!", res)
        }
    }

     

    위와 같이 사용해줄 수 있습니다.

     

    먼저 localhost:8080이 접근 가능할 때 실행 해봅시다.

     

    그리고 localhost:8080에 접근이 불가 할때 실행 시켜 봅니다.

     

    비활성화 된 테스트케이스를 확인 할 수 있습니다.

    반응형