implementation을 사용해야 하는 이유

sonjiseokk·2023년 10월 20일

문제 해결 방법은 맨 아래에 있다. 처음에는 도출 과정부터 시작한다.

📢 시작

현재 이 책을 통해 스프링부트를 다시 공부하고 있다.

이 책이 2020년에 쓰여진 책이라서 지금과 조금 다른 면이 많았고, 그러한 것들은 내 역량과 구글을 통해 어느정도 해결하면서 진행하고 있었다.

그런데 진짜 말도 안되는 오류가 발생했다.... 일단 코드들을 보여주겠다.

@SpringBootTest(webEnvironment = RANDOM_PORT)  
class IndexControllerTest {  

    @Autowired  
    private TestRestTemplate restTemplate;  
     
    @Test  
    @DisplayName("load_main_page")  
    void loadMainPage() throws Exception {  
        //when  
        String body = this.restTemplate.getForObject("/", String.class);  
        //then  
        assertThat(body).contains("스프링 부트로 시작하는 웹 서비스");  
    }
}

코드는 제법 간단하다.

  1. 마치 서버가 켜진 것처럼 사용할 수 있는 TestRestTemplate를 사용한다.
  2. SpringBootTest 를 통해 랜덤 포트 환경을 테스트한다.
  3. "/" 을 호출하여 받는 HTML을 String 형태로 저장한 후에 이 안에 다음과 같은 텍스트가 있는지 확인한다.

그러나 매우 큰 문제가 발생하였으니..


🔥 문제 발생

"{"timestamp":"2023-10-20T14:15:31.713+00:00","status":404,"error":"Not Found","path":"/"}"

계속해서 body에 이러한 식으로 데이터가 들어갔다.

이게 코드가 문제가 있다면 브라우저 환경에서는 작동하지 않아야 맞다. 그러나 그렇지 않았다. 브라우저 환경에서는 너무나도 정상적으로 작동했다.

여기서 부터 멘탈이 나가기 시작했다.. 구글에 많은 검색을 해보았지만 나와 같은 문제를 가진 사람은 찾을 수 없었다. 스택오버플로우도 검색어를 최대한 다양하게 하여 검색해보았지만 같았다. 심지어 GPT 조차도... 해결 방법은 안됐다.


❓ 문제가 무엇이냐?

정말 쌩뚱맞게도 build.grdle 이 문제였다.

이게 무슨 소리냐? 코드가 이상한거 아니냐? 당황스럽게도 코드는 너무나 정상적이였다.
문제는 compileOnlyimplementation 둘의 차이 때문에 발생했다.

나는 머스테치 라이브러리를 가져오면서
compileOnly('org.springframework.boot:spring-boot-starter-mustache') 를 사용하였는데 이게 문제를 발생시켰다.

결론만 말하자면
implementation 'org.springframework.boot:spring-boot-starter-mustache' 로 수정하면 해결된다.


💡 compileOnly와 implementation의 차이

compileOnly는 Gradle에서 해당 의존성을 컴파일 시점에만 사용하겠다는 의미이다. 따라서 런타임 시에는 포함되지 않는다.

implementation는 컴파일, 런타임 시점 모두 사용하겠다는 의미이다. 따라서 머스테치 라이브러리를 런타임에서 사용하겠다는 의미이다.

이건 다 재쳐두고 그럼 이런 질문이 나올 수 있다.

둘이 차이가 있건 말건 브라우저에서는 작동했다 했잖아? 그럼 왜 이게 문제인건데?

정답은 "스프링 부트" 였다.

스프링 부트가 너무 똑똑한 나머지 아오 애플리케이션을 실행할 때, 모든 클래스 패스를 사용하게 된다. 즉, compileOnly로 작성되어도 이를 런타임 시점에도 사용할 수 있게 도와준다는 것이다.

그러나 테스트 환경은 다르다.

이러한 동작은 스프링 부트를 통해 애플리케이션이 실행될 때 보장되기 때문에, 특히나 테스트 환경에서는 이것이 보장되기 힘들다. 즉 지정된 의존성이 런타임 클래스 패스에 포함되지 않는 것이다.

따라서 build.gradle 을 implementation 방식으로 수정했고 정상적인 결과를 받을 수 있었다..


✅ 결론

GPT의 말에 따르면 대부분의 경우에는 implementation을 사용하는 것이 낫다고 한다.

실제 프로덕션 환경에서 애플리케이션을 패키징하고 배포할 때에는 필요한 의존성이 모두 포함되도록 해야 하기 때문이라 한다.

이제부터는 무조건 implementation을 사용하는 습관을 가져야겠다...

profile
Software Engineer

0개의 댓글