Spring MVC 리팩토링 1
Service Layer의 인터페이스를 살펴보니 다음과 같습니다.
마지막에 이는 저 메소드의 이름과 하는 일이 맘에 안듭니다. 구현도 제가 했고 구현한지 2틀 정도밖에 안됐습니다. 더 늦기 전에 수정해야겠습니다.
public Member findByMail(String mail)
다분히 DAO Layer에 있어야 할 것 같은 이름이며 하는 일 역시 그저 DAO 계층에 있는 메소드 하나를 호출하는 겁니다. 본래 저 메소드를 만들게 된 의도는 해당 mail로 가입한 멤버가 있는지 확인하려는 의도였습니다. 하지만 저 메소드의 이름으로는 도무지 그런 의도가 안보입니다. 따라서 다음과 같이 수정해야 겠습니다.
public boolean isJoinedMail(String mail)
이러한 인터페이스로 변경되어야 겠습니다. 근데 어디 부터 손을 대야 할까요;;;;
일단 Controller로 가봐야겠습니다. 그곳에서 Service Layer의 메소드를 호출하고 있기 때문에 그곳에서 새로운 isJoinedMail(mail)을 호출하도록 수정하여 구현을 변경하면 될 것 같습니다.
아니군요. Controller로 바로 가지 않고 해당 컨트롤러의 테스트 클래스로 갑니다. 이 곳이야 말로 진짜 리팩토링을 시작할 지점입니다.
수정 전 테스트 메소드 입니다.
mail = "";
expect(mockMemberService.findByMail(mail)).andReturn(null);
replay(mockMemberService);
command.setMail(mail);
ModelAndView mav = controller.onSubmit(null, null, command, null);
assertEquals("redirect:join.html", mav.getViewName());
assertViewName(mav, "redirect:join.html");
verify(mockMemberService);
}
수정 후 테스트 메소드 입니다.
mail = "";
expect(mockMemberService.isJoined(mail)).andReturn(false);
replay(mockMemberService);
command.setMail(mail);
ModelAndView mav = controller.onSubmit(null, null, command, null);
assertEquals("redirect:join.html", mav.getViewName());
assertViewName(mav, "redirect:join.html");
verify(mockMemberService);
}
매우 간단한 변경입니다. 위에서 언급한 것과 하나 차이가 있다면 메소드 명을 isJoined()로 변경하였습니다. 어차피 가입 확인을 mail로 만 할 것이기 때문에 굳이 적어줄 필요가 없다고 느꼈습니다. 이 메소드를 사용할 때 다음과 같이 이쁜 문장이 만들어 지겠죠. :-)
isJoined(mail);
테스트가 실패한 이유는 컨트롤러를 테스트 할 때(onSubmit()이 호출 됐을 때) 각본에는 MemberService의 isJoined()가 주인공으로 열연을 펼친뒤 false를 리턴하라고 했는데 실제로 재생을 해보니까 주연은 커녕 엑스트라로도 보이지 않았기 때문입니다.
자 이제 이 테스트가 돌아갈 수 있도록 컨트롤러의 구현을 변경합니다.
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException exception) throws Exception {
MemberCommand memberCommand = (MemberCommand)command;
if(memberService.isJoined(memberCommand.getMail()) != null)
return new ModelAndView(getSuccessView())
.addObject("member", member);
else
return new ModelAndView("redirect:join.html");
}
이런 문제가 생겼습니다. isJoined() 메소드를 사용하여 boolean 값을 받아왔는데 그걸로는 View에 넘겨줄 정보가 부족합니다. 다시 말하면 View에서는 member 객체를 필요로 하는데 여기서는 그 객체를 받아 오려면 memberService에 새로운 mail 로 해당 member를 가져오는 것을 구현해야 할 것 같습니다.
이럴 때 혼자 개발할 때의 장점이 저를 살려줍니다.
"니가 곧 개발자며 사용자이자 설계자다. 니가 다 알아서 해라!"
저는 isJoined(mail)의 인터페이스를 다시 한번 변경합니다.
public Member isJoined(mail)
만약 해당 mail 이 없다면 null을 반환합니다. 따라서 컨틀롤러의 테스트 코드르 다시 수정합니다.
mail = "";
expect(mockMemberService.isJoined(mail)).andReturn(null);
replay(mockMemberService);
command.setMail(mail);
ModelAndView mav = controller.onSubmit(null, null, command, null);
assertEquals("redirect:join.html", mav.getViewName());
assertViewName(mav, "redirect:join.html");
verify(mockMemberService);
}
이제 다시 컨트롤러의 구현을 수정합니다.
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException exception) throws Exception {
MemberCommand memberCommand = (MemberCommand)command;
Member member = memberService.isJoined(memberCommand.getMail());
if(member != null)
return new ModelAndView(getSuccessView())
.addObject("member", member);
else
return new ModelAndView("redirect:join.html");
}
끝!! 테스트는 돌아갑니다.
이제 남은 일은 Controller를 단위 테스트가 아닌 통합 테스트를 해보는 것입니다. 그러면 당연히 에러가 나겠죠. 아직 MemberService와 MemberDAO 구현을 변경하지 않았기 때문이죠. 이 때 두가지 선택 사항이 있습니다.
1. Controller를 통합 테스트 해버려서 구현한다
2. MemberService를 단위 테스트 -> MemberDAO를 단위(?) 테스트 -> MemberService 통합 테스트 -> Controller 통합테스트
두 가지 선택 사항이 있습니다. 테스팅 공부를 제대로 할려면 돌아가야겠죠. 저는 2번을 선택하겠습니다.