[Spring] TDD 도입기 - 2. 아직은 잘 모르겠어요
안녕하세요. 오늘은 저번에 쓰던 글을 마무리해보겠습니다.
이번주에 면접을 보고와서 기력이 전부 소진된 느낌이지민 할 건 해야죠? 되게 담담하게 말을 하고 있지만 사실 저의 인생 첫 직장 면접이었습니다. 제 심장도 놀랐을 걸요?? 그렇게 빠르게 뛸 수 있었다니.. 코딩 시연이 면접 과정에 포함되어 있어서 코딩을 하는데 노트북이 땀으로 흥건해졌습니다.
이렇게 된거 면접에 대한 이야기를 좀 더 하고 싶지만 늘 삼천포로 글을 쓰다 보니까 자소서를 쓸 때도 의심의 흐름대로 작성하게 돼서 오늘은 주제에 벗어나지 않는 게 목표입니다. 목표를 세우자마자 더 쓰고 싶어 지는데요? 참아
저번에 Controller 계층의 Test 코드 기본 환경 세팅까지 작성했었습니다.
Controller
기본적인 TDD 코드 작성 방식에 대해 참고한 블로그 입니다.
https://mangkyu.tistory.com/182
[TDD] 단위 테스트와 TDD(테스트 주도 개발) 프로그래밍 방법 소개 - (1/5)
이번에는 여러 개발 서적들 및 실무 경험 그리고 시행 착오 등을 겪으면서 얻은 테스트 주도 개발 방법에 대해 소개해보고자 합니다. 이번 포스팅에서는 먼저 단위 테스트와 중요성 그리고 단위
mangkyu.tistory.com
MockMvc 를 사용하기 위해 아래와 같이 매 테스트 시작 전 값을 넣어주고 있습니다.
근데 저는 이런 방식으로 mockMvc를 만들어주는 이유가 궁금했습니다.
위의 테스트 코드를 보아하니 MockMvc는 다른 테스트에서도 사용이 될 것으로 보인다. 그러므로 각각의 테스트마다 독립적으로 해당 객체를 만들어주면 좋을 텐데, 이를 위해 각각의 테스트가 실행되기 전에 초기화를 도와주는 @BeforeEach를 사용하도록 하자.
출처: https://mangkyu.tistory.com/184
[MangKyu's Diary:티스토리]
mockMvc를 전역으로 사용하지 않는 이유에 대해 아래은 그냥 취향차이다~라는 의견이 있는데.. 뭐가 아직 께름칙하네요
Why do MockMvc tests suggest injecting WebApplicationContext @BeforeEach test?
So the reference documentation suggests @BeforeEach void setup( WebApplicationContext wac) { this.mockMvc = MockMvcBuilders.webAppContextSetup( wac ) .apply(
stackoverflow.com
제 생각에는 현재 Controller 테스트 코드 작성 시에는 @BeforeAll에 전역변수로 mockMvc를 한 번만 잡아주는 것이 더 깔끔해 보이긴 합니다.
+ 위 코드는 테스트에 실패합니다..?
"Cannot invoke \"com.agn.server.board.BoardService.saveEvent(java.util.UUID, String, String, String)\" because \"this.boardService\" is null"
mockMvc의 standaloneSetup에 BoardController.class를 넣어 줘서 BoardService에 대한 의존성 주입이 안된 것으로 보입니다. @Mock과 @InjecMock 어노테이션은 인스턴스 레벨에서 동작하기 때문에 static 접근 제어자에서는 사용이 불가능합니다. 따라서 @BeforeAll를 사용하기 위해서는
이런 식으로 코드를 작성해야 합니다. ㅠ
말 나온 김에 @BeforeEach와 @BeforeAll에 대해서 사용 상황에 대해서 확실하게 짚고 넘어가면 좋을 것 같습니다.
@BeforeEach는 좋은 테스트를 만들기 위해서는 이전의 테스트가 다른 테스트의 영향을 받아서는 안된다는 조건에 적합한 어노테이션입니다. 테스트들을 독립적으로 같은 환경으로 만듭니다. 위와 같은 상황에서 mockMvc 객체는 모든 테스트에서 사용되기는 하지만 모든 테스트에서 사용된다고 해서 하나의 객체로 만들어 버리는 것이 좋은 테스트 코드라고 생각하기는 어려워 보입니다.
하지만 좋은 테스트의 조건 중 '테스트는 시간이 오래 걸리지 않아야 한다'라는 조건도 있습니다. 따라서 모든 설정 작업을 @BeforeEach에서 작업하는 것은 위의 조건에 부합하지 않습니다. 이에 대해서 너무 좋은 글을 찾았습니다.
@BeforeAll을 사용하면 함수가 한 번만 실행되므로, 모든 테스트에 한 번만 로딩되어야 하는 데이터를 로딩할 때 혹은 테스트에 설정될 불변값이 로딩되어야 하는 경우 사용된다.
출처: https://kotlinworld.com/472?category=1055014#%40BeforeEach의 한계와 %40BeforeAll의 사용처-1 [조세영의 Kotlin World:티스토리]
그럼 다시 본론으로 들어와서 mockMvc가 각 테스트에 영향을 주는 기능을 사용하지는 않을 것으로 추측만 되기 때문에 BeforeEach로 관리하는 것이 더 확실해 보입니다.
그럼 이제 진짜 본격적으로 테스트 코드를 보겠습니다..
이 코드를 작성하면서 고민한 부분은 EventReqeust에 어떤 어노테이션을 붙여줘야 하나 고민했었습니다.
eventReqesut는 json 형태가 자동으로 eventReqeust로 만들어지기 때문에 따로 Builder가 필요하지 않은 객체라고 생각돼서 기본 생성자와 allargumentsr가 있는 생성자를 만드는 어노테이션만 붙여줬습니다.
그리고 레퍼런스에서는 응답 상태 값만을 검증하고 있는데, 저는 응답 값에 적절한 정보가 들어가는지에 대해서도 테스트를 하고 싶었습니다.
이를 구현하기 위해서는 ResultActions에 어떤 값이 담겨있는지 확인을 해야 합니다. resultAction.andDo(print())를 통하여 담겨있는 값들을 콘솔에서 확인할 수 있습니다.
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/event/create
Parameters = {}
Headers = [Content-Type:"application/json", memberId:"4df81983-cbc7-4977-96d7-92b394823c3e", Content-Length:"54"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.agn.server.board.BoardController
Method = com.agn.server.board.BoardController#createEvent(String, EventRequest)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json"]
Content type = application/json
Body = {"httpStatus":"CREATED","path":"api/event/create","data":{"postUUID":null,"title":"title","contents":"contents","type":"EVENT","delYN":false,"member":null,"createdTime":null,"updatedTime":null}}
Forwarded URL = null
Redirected URL = null
Cookies = []
body 값에 대한 비교가 필요함으로 jsonPath( )를 사용하여 그 값을 확인해 줬습니다.
ResultAction의 사용법에 대한 방법은 https://velog.io/@appti/ResultActions
ResultActions
ResultActions
velog.io
이분 블로그를 참조하여 코드를 작성하였습니다.
+ 근데
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json"]
Content type = application/json
Body = {"httpStatus":"CREATED","path":"api/event/create","data":{"postUUID":null,"title":"title","contents":"contents","type":"EVENT","delYN":false,"member":null,"createdTime":null,"updatedTime":null}}
Forwarded URL = null
Redirected URL = null
Cookies = []
이 부분에서 Status와 body 안에 Status가 서로 다르게 설정되어있는 부분을 고치고 싶네요.
팀원분이 작성한 코드라서.. 추후 의견을 물어보고 코드를 고쳐보기로 하겠습니다.
이제 create에 대한 TDD 개발이 끝난 것 같습니다. CRUD가 이렇게 힘든 작업이었다니... 복잡한 서비스 기능에 대해 Test코드 작성이 벌써 두렵 아니 기대가 됩니다.