[인프런 워밍업 클럽 2기 - BE] 3주차 발자국

[인프런 워밍업 클럽 2기 - BE] 3주차 발자국

[인프런 워밍업 클럽 2기 - BE] 3주차 발자국

image

이 블로그는 정보근님의 입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기 강의 기반으로 코드작성과 코드설명을 적었습니다



1. controller test 코드 분석

애너테이션 조사

  • @AutoConfigureMockMvc

    • MockMvc를 자동으로 설정해 주는 애너테이션. 이 애너테이션을 통해 HTTP 요청을 수행하고 응답을 확인할 수 있다.

  • MockMvc 란 ?

    • 실제로 서버를 띄우지 않고 컨트롤러를 테스트할 수 있는 도구

  • @DisplayName("Test")

    • 테스트 클래스를 시작할 때 테스트 클래스의 이름을 지정해서 테스트 리포트에 표시된다.

    • 위 코드를 실행시키면 리포트에 TEST 라는 이름으로 테스트 성공유무가 표시됨.

  • @Configuration : Spring에서 Bean을 수동으로 등록하기 위해서 사용

메서드 분석

 

@Test
@DisplayName("Introductions 조회")
fun testGetIntroductions() {
    val uri = "/api/v1/introductions"

    val mvcResult = performGet(uri)
    val contentAsString = mvcResult.response.getContentAsString(StandardCharsets.UTF_8)
    val jsonArray = JSONArray(contentAsString)

    assertThat(jsonArray.length()).isPositive()
}
  • api/vi/introduction을 uri 변수에 넣음.

  • HTTP GET 요청을 보낸후, 응답을 문자열로 변환한 후에 JSONArray로 변환

  • JSONArray의 길이가 0보다 큰지 검증, 응답 데이터가 비어 있지 않음을 확인

 

@Test
@DisplayName("Link 조회")
fun testGetLinks() {
    val uri = "/api/v1/links"

    val mvcResult = performGet(uri)
    val contentAsString = mvcResult.response.getContentAsString(StandardCharsets.UTF_8)
    val jsonArray = JSONArray(contentAsString)

    assertThat(jsonArray.length()).isPositive()
}
  • api/vi/links을 uri 변수에 넣음

  • HTTP GET 요청을 보낸후, 응답을 JSONArray로 변환

  • 배열의 크기와, 그 데이터가 존재하는지 검증

@Test
@DisplayName("Resume 조회")
fun testGetResume() {
    val uri = "/api/v1/resume"

    val mvcResult = performGet(uri)
    val contentAsString = mvcResult.response.getContentAsString(StandardCharsets.UTF_8)
    val jsonObject = JSONObject(contentAsString)

    assertThat(jsonObject.optJSONArray("experiences").length()).isPositive()
    assertThat(jsonObject.optJSONArray("achievements").length()).isPositive()
    assertThat(jsonObject.optJSONArray("skills").length()).isPositive()
}
  • /api/vi/resume를 uri변수에 넣음

  • GET요청을 보낸후, 응답을 JSONObject로 변환

  • experiences, achievements,skills를 JSONArray로 변환 후 그 값이 양수인지와 데이터가 존재하는지 검증

@Test
@DisplayName("Projects 조회")
fun testProjects() {
    val uri = "/api/v1/projects"

    val mvcResult = performGet(uri)
    val contentAsString = mvcResult.response.getContentAsString(StandardCharsets.UTF_8)
    val jsonArray = JSONArray(contentAsString)

    assertThat(jsonArray.length()).isPositive()
}
  • /api/vi/projects를 uri 변수에 넣음

  • GET요청을 보낸후 응답 본문을 JSONArray로 변환하고, 배열의 길이가 양수인지와 데이터가 존재하는지 검증

private fun performGet(uri: String): MvcResult {
    return mockMvc
        .perform(MockMvcRequestBuilders.get(uri))
        .andDo(MockMvcResultHandlers.print())
        .andReturn()
}
  • perform(MockMvcRequestBuilders.get(uri)) : 특정 uri에 GET 요청을 보내는 코드. 서버를 띄우지 않고 API 엔드포인트의 동작을 확인할 수 있음

  • andDo(MockMvcResultHandlers.print()) : 요청과 응답을 콘솔에 출력

  • andReturn() : MvcResult를 반환


2. 부분 코드 분석

<html *lang*="ko" *xmlns:th*="<http://www.thymeleaf.org>" *th:replace*="~{presentation/layouts/layout-main :: layout(~{::#content})}">
  • Thymeleaf 템플릿 엔진에서 레이아웃을 정의하고 해당 레이아웃을 재사용하는 방식, 템플릿 코드의 중복을 줄이고, HTML 파일 간에 공통 요소를 재사용할 수 있게 하는 것.

 

th:replace="~{presentation/layouts/layout-main :: layout(~{::#content})}"
  • th:replace : 다른 파일을 가져와 현재 위치에 삽입하는 기능 수행

  • ~{presentation/layouts/layout-main :: layout(~{::#content})} :

    • ~{presentation/layouts/layout-main} : layout-main.html 파일 참조

    • :: layout : layout 이라는 이름의 fragment를 사용하겠다는 의미, 위 코드에서는 layout fragment를 가져오겠다는 뜻

    • (~{::#content}) : id="content" 로 지정된 부분을 layout fragment의 특정 위치에 대체할 것이라는 의미


3. interceptor 코드 분석

@Component
class PresentationInterceptor(
        private val httpInterfaceRepository: HttpInterfaceRepository
) : HandlerInterceptor {
    override fun afterCompletion(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception?) {
        val httpInterface = HttpInterface(request)
        httpInterfaceRepository.save(httpInterface)
    }
}
  • private val httpInterfaceRepository: HttpInterfaceRepository

    • 의존성 주입을 통해서 Repository를 사용할 수 있다.

  • override fun afterCompletion(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception?) {

    • afterCompletion : HTTP 요청 처리 후에 호출되는 메서드

  • val httpInterface = HttpInterface(request)

    httpInterfaceRepository.save(httpInterface)

    • request 정보를 담고 있는 httpInterface 객체를 생성 후, httpInterfaceRepository에 저장.

 

@Configuration
class PresentationInterceptorConfiguration(
        private val presentationInterceptor: PresentationInterceptor
) : WebMvcConfigurer {
    override fun addInterceptors(registry: InterceptorRegistry) {
        registry.addInterceptor(presentationInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/assets/**", "/css/**", "/js/**", "/admin/**", "h2**",
                        "/favicon.ico", "/error")
    }
}
  • addInterceptors

    • 인터셉터를 등록하는 메서드

  • registry.addInterceptor(presentationInterceptor)

    • addInterceptor 메서드를 통해 presentationInterceptor를 등록

  • addPathPatterns("/**")

    • 모든 요청 경로에 대해 인터셉터 적용

  • excludePathPatterns(...) : 인터셉터가 동작하지 않도록 설정

    • /assets/**: 정적 자원(예: 이미지, 폰트 등).

    • /css/**: CSS 파일.

    • /js/**: JavaScript 파일.

    • /admin/**: 관리 페이지.

    • h2**: H2 데이터베이스 콘솔.

    • /favicon.ico: 사이트 아이콘.

    • /error: 오류 처리 경로.


[미션4] 조회 REST API 만들기-회고

이번 미션을 하면서 @GetMapping 어노테이션에 대해서는 어느정도 이해를 할 수 있었다. 저번 발자국에서 @Id, @GeneratedValue 어노테이션에 대해서 적었지만 아직 부족하다는 걸 알 수 있었고, @ManyToOne 어노테이션에 대해 더 깊이 공부해야겠다는걸 깨달았다. 아직 Spring에 본질적인걸 이해를 못한걸수도 있는거 같다. 코드를 작성하면 할수록 더 깊게 공부를 해야했고, JAVA 문법도 다시 해야겠다는걸 뼈저리게 느끼면서 한것 같다...


3주차 회고

Spring은 하면 할수록 재밌는건 맞다. Test 코드를 작성할 때도 이래서 이코드가 작동이 되는것도 알 수 있고, 부분적인 걸 따로 모듈화? 해서 만드는것도 재밌다. 이번 워밍업 클럽도 곧 종료가 되는데 java의 중요성도 깨달아서 java공부도 열심히 하고, Spring에 대해서 더 깊게 공부할거다!

참고로 중간점검 때 정보근님께서 내가 작성한 질문에 대해 답변을 해주신거 같다. 나의 질문은 이번 워밍업 클럽이 종료가 되면 java의 정석, spring공부를 할건데 추천해줄 강의가 있냐 라는 질문이었다. 답변은 역시 김영한님의 강의였고 다른 강의도 추천해주셨는데 이거는 확인해보고 글을 수정해야겠다...! 무튼, spring을 선택한건 잘한 일 같다...!

댓글을 작성해보세요.

채널톡 아이콘