스프링 2.5 @MVC 컨트롤러 테스트관련 궁금한거..

@Controller
@RequestMapping(“board/*.do”)
@SessionAttributes(value=”board”)
public class BoardController {

    @Autowired
    private BoardService boardService;

    @Autowired
    private BoardValidator validator;

    @RequestMapping
    public void list(ModelMap model){
        model.addAttribute(boardService.getAll());
    }

    @RequestMapping(method=RequestMethod.GET)
    public void add(ModelMap model){
        model.addAttribute(new Board());
    }

    @RequestMapping(method=RequestMethod.POST)
    public String add(@ModelAttribute(“board”) Board board, BindingResult result, SessionStatus status){
        validator.validate(board, result);
        if(result.hasErrors())
            return “board/add”;
        else{
            this.boardService.saveOrUpdate(board);
            status.setComplete();
            return “redirect:board/list.do”;
        }
    }

    @RequestMapping
    public void delete(int id){
        boardService.deleteById(id);
    }

}

위 코드는 간단한 스프링 2.5 @MVC 컨트롤러입니다. CRUD 중에서 U 관련 코드는 생략했습니다.

이 컨트롤러를 작성하면서 확인하고 싶었던 걸 정리하면 다음과 같습니다.
1. board/list.do 라는 요청 결과를 보여줄 뷰는 WEB-INF/board/list.jsp 파일이 맞는지..(ViewResolver 확인)
2. board/list.do 라는 요청을 했을 때 결과 뷰에 넘어가는 모델 객체 중에 List<Board>가 있는지. 그리고 그 모델 객체의 이름이 boardList가 맞는지.(ModelAndView 확인)
3. board/add.do GET 요청이 오면 저 컨트롤러의 add(ModelMap) 메소드가 호출되는지.(RequestMapping 확인)

이 때 2번은 쉽게 테스트가 가능합니다. 인자로 넘겨주는 ModelMap 객체를 하나 만들어서 넘겨주고 해당 메소드를 호출 해보고 ModelMap 객체를 뒤져보면 테스트 하고자 하는 데이터를 찾을 수 있습니다.

그러나.. 1번은 ModelAndVIew 객체를 반환하도록 컨트롤러 코드를 고치지 않는 이상 어떻게 테스트 할 수 있을지 감이 안잡힙니다. 스프링 MVC CoC를 적극 활용하려고 작성한 코드인데 명시적으로 뷰이름을 설정해버리면 그런 의도가 무색해지니까요..

3번도 마찬가지 입니다. 이건 더 감이 안 잡힙니다. 테스트를 할 때 만드는 ApplicationContext가 WebApplicationContext도 아니라서 스프링이 기본으로 등록해주는 빈(request handler, adapter 등)들이 없을 것이기 때문에 스프링 테스트만 가지고는 테스트가 어려울 것 같다는 생각이 듭니다.

어쩌면 컨트롤러 테스트가 아닐 수도 있겠습니다. 스프링 프레임워크가 제공하는 기능에 대한 테스트일 수도 있으니 스프링 코드에서 뷰 리졸버와 요청 맵핑 클래스에 대한 테스트로 만족하고 넘어갈 수도 있겠지만.. 글쎄요. 그래도 왠지 테스트가 하고 싶네요. 혹시 HttpUnit으로 이런걸 할 수 있는건가요. 써보지 않았는데.. 함 살펴봐야겠습니다.

Spring MVC 리팩토링 4

이전에서 단위테스트를 통해 Controller과 Service, DAO 계층을 테스트 했습니다.

  1. 13:17:31 Spring MVC 리팩토링 3
  2. 13:04:41 Spring MVC 리팩토링 2
  3. 12:28:58 Spring MVC 리팩토링 1

사실 DAO 계층 테스트를 단위테스트라고 할 수 있을지 모르겠지만 테스트용 DB와 테스트 데이터를 별로도 사용해서 실제 DB에는 전혀 지장이 없었습니다. 그리고 spring-mock.jar에 있는 AbstractTransactionalDataSourceSpringContextTests를 사용하여 자동 롤백이 되도록 하여 테스트의 흔적이 테스트 DB에 남지도 않았습니다. 그러면서도 DAO를 충분히 테스트 할 수 있었습니다.

이제는 Service Layer에서 DAO 계층과 함꼐 테스트를 해보고 그 다음은 Controller 에서 그 아래에 있는 Service 계층과 DAO 계층을 아울러서 테스트 할 차례입니다.
사용자 삽입 이미지먼저 위 그림의 파란색은 이전에 테스트를 했고 빨간색 부분의 테스트를 작성합니다. 이 때도 이전 글에서 사용한 osaf의 클래스를 사용합니다.

public class MemberServiceIntegrationTest extends AbstractTransactionalDataSourceSpringContextControllerTest{

    protected MemberService memberService;

    @Override
    protected String[] getConfigLocations() {
        return new String[] {
            “file:web/WEB-INF/spring/applicationContext-dao.xml”,
            “file:test/applicationContext-jdbc-datasource.xml”,
            “file:web/WEB-INF/spring/dao/daoContext-member.xml”,
            “file:web/WEB-INF/spring/application/applicationContext-member.xml”,
        };
    }

    @Override
    protected void onSetUpInTransaction() throws Exception {
        insertFlatXmlDataSet(“test/kr/co/springframework/member/dao/memberData.xml”);
    }

    public void testDI() throws Exception {
        assertNotNull(memberService);
    }

    public void testGetAll() throws Exception {
        assertEquals(1, memberService.getAll().size());
    }

    public void testNotJoinedMail() throws Exception {
        Member member = memberService.isJoined(“”);
        assertEquals(null, member);
        assertEquals(“기선”, memberService.isJoined(“keesun@mail.com”).getName());
    }
}

간단하게 테스트 할 수 있습니다. 단위 테스트하고의 차이는 Spring의 컨테이너를 사용하여 의존성이 삽입된 객체를 사용하고 있으며 DB역시 DBUnit을 이용하여 사용하고 있습니다. 테스트용 DB라는 것만 빼고는 전부 같습니다. 아… 테스트용 DB(HSQL)와 실제 개발 DB(MySQL)와 다르군요. 하지만 ORM(하이버네이트)를 사용하고 있기 때문에 전~혀 문제가 없습니다. 테스트용 database.properties 파일 하나만 작성해주면 되죠.

사용자 삽입 이미지이번에는 컨트롤러를 테스트 합니다. 이것도 역시 이전에 파란색 박스는 테스트를 했고 이번에는 빨간 박스를 테스트 합니다.

    public void testEmptyOrWhiteMail() throws Exception {
        mail = “”;
        MemberCommand command = new MemberCommand();
        command.setMail(mail);
        mav = checkController.onSubmit(null, null, command, null);
        assertEquals(“redirect:join.html”, mav.getViewName());
    }

    public void testExistMemberMail() throws Exception {
        mail = “keesun@mail.com”;
        mav = checkController.onSubmit(null, null, command, null);
        Map model = mav.getModel();
        assertNotNull(model.get(“member”));
        assertEquals(“confirm”, mav.getViewName());
    }

서비스 계층의 통합 테스트 코드와 상당부분이 같기 때문에 테스트 메소드만 붙여넣었습니다. (테스트 코드에서만 볼 수 있는)단위 테스트와의 차이는 easymock의 사용이 없어졌다는 것입니다. 이전에는 MemberService의 Mock객체를 만들어 사용했었습니다. 하지만 이제는 진짜 MemberSerive 객체를 사용하고 있죠.

이것으로 매~~~우 간단한 리팩토링(메소드 이름 하나 바꾸는 것)을 매우 길~~~~게 돌아오며 테스트를 연습해 봤습니다.

요약 및 느낀점
– Easy Mock은 생각보다 간단하다.(createMock() -> expect() -> replay() -> verify() -> reset())
– spring-mock.jar 에 유용한 Mock 객체들(Spring MVC 책에서는 Stub이라고 얘기하고 있지만)이 많이 있는데 그 중에 자주 사용할 것은 MockHttpServletRequest와 MockHttpServletResponse
– 계층 별 테스트 할 대상, 목록, 요점을 명확히 해야겠다.
– 테스팅(단위 테스트와 통합 테스트)은 확실히 애플리케이션에 자신감을 불어넣어준다.
– View를 테스트 하는 방법은 무엇일까? (Spring view test라고 구글링을 해봤지만 원하는 결과는 못봤다.)
– 컨트롤러를 테스트 할 때 데이터 바인딩과 벨리데이션 역시 테스트 해야겠다.
– 테스트 코드를 작성할 때 참고할 명세서가 있어야겠다.(비즈니스 로직을 자세히 설명한 어떤 문서…)