티스토리 뷰

객체지향, 자바 & 스프링을 올바르게 공부하고 있는 건가 싶은 의문이 든다면 아래 동영상을 한번 보자

 

 

얼마 전 팀장님과 잠깐 나눴던 대화 중 앉아만 있다고 공부하는 것이 아니라는 말씀을 해주셨다

난 2021.02 기준으로 개발 공부를 시작했고 그때부터 약간은 강박적으로 공부에 매달렸다

이전 회사에서도 그랬고 지금도 퇴근 후 적어도 4시간 이상은 하고 주말에 약속이 없다면 거진 10시간 정도를 하는데

그래서 난 공부를 오래 하고, 정말 열심히 한다는 생각을 했던 것 같다

팀장님의 말씀과 위 동영상을 보고나니 의식적인 연습이 부족했구나 하는 생각이 든다

 

그렇게 긴 시간을 공부하더라도 무의식적인 공부라면 코드 따라 치기 일 뿐이고 잘 봐줘도 API 사용법 학습일 뿐이다

위 동영상에서 말한 것과 팀장님께서 해주신 말씀에서 강조하신 것은 결국 하나다

코드 한줄을 작성하더라도 계속해서 생각해야 하고 깊게 들어가려 하면 정말 끝이 없다는 것이다

어떤 부분에서 터질지, 어떻게 그걸 막을지, 어떻게 해야 테스트하기 쉽게 만들 수 있을지

어떻게 성능을 올릴지, 어떻게 하면 더 가독성을 좋게 만들지, 어떻게 해야 더 세련된 API를 사용할지..

늦지 않았다, 이제부터라도 의식적으로 학습하고 써먹어보자

 

최근 OAuth2 학습을 위한 사이드 프로젝트를 진행하는데 구글에서 토큰을 받아와야 하는 로직이 있다

구글 콘솔에서 callback-url을 설정해놓으면 OAuth2 인증이 완료된 후 등록해놓은 url로 결과를 내려준다

access-token을 요청할 수 있는 code를 내려주기 때문에 이 녀석을 가지고 다시 한번 요청해야 한다

따라서 callback-url로 매핑해놓은 Controller에서 토큰 요청할 로직을 수행하면 되니

Controller에서 직접 WebClient에 의존해두고 요청했다

 

여기까지 작성하면 동영상에서 말한 상황이 그대로 펼쳐진다

동영상은 GitHub API를 사용해 작성해놓은 상태에서 어떻게 테스트를 할 수 있게 만들 것인가에 관해서다

1. 테스트할 때마다 GitHub API 사용한다

-> 테스트할 때마다 외부 API를 쏴야한다

2. PSA (portable service abstraction)를 사용한다

-> 테스트용 클래스를 따로 만들어 끼워준다

 

PSA란 서비스(로직) 자체를 바꿔 낄 수 있게 추상화하는 것이다

내 상황에 빗대어 설명하면 WebClient로 구글에 쏘는 그 과정을 Controller에서 직접 수행하지 않고

TokenService라는 인터페이스를 만든다

TokenService를 구현한 RealTokenService에서 구글로 쏘는 로직을 수행한다

TokenController에서 TokenService를 Interface로 주입받아 얘를 이용해 토큰 요청하는 것이다

 

테스트를 위한 코드 작성도 간단해진다

TokenService를 구현한 MockTokenService에서 테스트를 위한 로직을 만들어둔다

테스트 코드에서는 RealTokenService 대신 MockTokenService를 주입 받아 사용한다

 

이렇게 되면 WebClient에 대한 의존성을 끊어낼 수 있고 인터페이스를 사용하기 때문에 확장성의 기초를 만들어둔 것이다

사이드 프로젝트에서 아래 코드와 같은 방식으로 실제 코드를 구현해뒀다, 이해하기 쉽도록 pseudo code로 첨부한다

// interface
public interface TokenService {

  Token getToken(String code);
}

// 실제 로직을 담은 서비스
@Primary
@Service
@RequiredArgsConstructor
public class RealTokenService implements TokenService {

  private final WebClient webClient;

  @Override
  public Token getToken(String code) {
    // webclient로 실제 쏘는 로직
  }
}

// controller
@Slf4j
@RestController
@RequiredArgsConstructor
public class TokenController {

  private final TokenService tokenService;

  @GetMapping(callback-url)
  public Token tokenPlease(@RequestParam String code) {
    return tokenService.getToken(code);
  }
}

// 테스트를 위한 Mocking 서비스
@Service
public class MockTokenService implements TokenService {

  @Override
  public Token getToken(String code) {
    // 테스트용으로 직접 Token을 만들어 반환해주는 로직
    return new Token("blahblah");
  }
}

 

보너스로 어떻게 테스트를 작성하는지도 남겨둔다

의존성을 넣어주는 과정은 TokenController에 TokenService의 구현체를 넣어주면 된다

따라서 테스트 과정에서는 직접 MockTokenService를 넣어주도록 한다

 

Mockito를 이용할 때 의존성 주입에 @MockBean과 @SpyBean이 있는데

로직이 실제로 수행되어야 할 때는 @SpyBean을 사용해야 한다, @MockBean은 의존성만 해결해주는 것이다

이 부분은 MockBean vs SpyBean 키워드로 찾아보는 것을 추천한다

Spring Security가 필요 없을 땐 @WebMvcTest 옵션으로 excludeFilters를 이용하면 된다

@WebMvcTest(controllers = TokenController.class, excludeFilters = {
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class)
})
@AutoConfigureMockMvc
class TokenControllerTest {

  @Autowired
  MockMvc mockMvc;
  @SpyBean
  MockTokenService mockTokenService;

  @Test
  @WithMockUser
  void tokenPlease() throws Exception {
    mockMvc.perform(
            get(callback-url)
                .param("code", "1234")
        )
        .andExpect(status().isOk())
        .andExpect(jsonPath("scope").exists())
        .andExpect(jsonPath("token_type").exists())
        .andExpect(jsonPath("expires_in").exists())
        .andExpect(jsonPath("access_token").exists())
        .andExpect(jsonPath("refresh_token").exists())
        .andDo(print());
  }
}
댓글
링크
글 보관함
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday