Keep going
프레젠테이션(웹) 계층의 CRUD 구현 본문
영속 계층, 비즈니스 계층의 구현까지 모든 테스트가 진행되었다면 프레젠테이션 계층인 웹의 구현을 해야 한다.
10.1 Controller의 작성
스프링 MVC의 Controller는 하나의 클래스 내에서 여러 메서드를 작성하고, @RequestMapping 등을 이용해서 URL을 분기하는 구조로 작성할 수 있기 때문에, 하나의 클래스에서 필요한 만큼 메서드의 분기를 이용하는 구조로 작성한다.
과거에는 이 단계에서 Tomcat(WAS)을 실행하고 웹 화면을 만들어서 결과를 확인하는 방식의 코드를 작성했다.
이 방식은 시간도 올래 걸리고 테스트를 자동화 하기에 어려움이 있다.
따라서 WAS를 실행하지 않고 Controller를 테스트할 수 있는 방법을 실습해보겠다.
10.1.1 BoardController의 분석
작성하기 전에는 원하는 기능을 호출하는 방식에 대해 테이블로 정리한 후 코드를 작성하는 것이 좋다.
Task | URL | Method | Parameter | From | URL 이동 |
전체 목록 | /board/list | GET | |||
등록 처리 | /board/register | POST | 모든 항목 | 입력화면 필요 | 이동 |
조회 | /board/read | GET | bno=123 | ||
삭제 처리 | /board/remove | POST | bno | 입력화면 필요 | 이동 |
수정 처리 | /board/modify | POST | 모든 항목 | 입력화면 필요 | 이동 |
테이블에서 From 항목은 해당 URL을 호출하기 위해서 별도의 입력화면이 필요하다는 것을 의미한다.
10.2 BoardController의 작성
BoardController는 org.zercok.controller 패키지에 선언하고 URL 분석된 내용들을 반영하는 메서드를 설계한다.
package org.zerock.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
@RequestMapping("/board/*")
public class BoardController {
}
|
cs |
BoardController는 @Controller 어노테이션을 추가해서 스프링의 빈으로 인식할 수 있게 하고, @RequestMapping 을 통해서 '/board'로 시작하는 모든 처리를 BoardController가 하도록 지정한다.
BoardController가 속한 org.zerock.controller 패캐지는 servlet-context.xml에 기본적으로 설정되어 있다.
10.2.1 목록에 대한 처리와 테스트
BoardController에서 전체 목록을 가져오는 처리를 먼저 작성한다.
BoardController는 BoardService 타입의 객체와 같이 연동해야 하므로 의존성에 대한 처리도 같이 진행한다.
package org.zerock.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.zerock.service.BoardService;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
private BoardService service;
@GetMapping("/list")
public void list(Model model) {
log.info("list");
model.addAttribute("list", service.getList());
}
}
|
cs |
BoardController는 BoardService에 대해서 의존적이므로 @AllArgsContstructor를 이용해서 생성자를 만들고 자동으로 주입하도록 한다.
list()는 나중에 게시물의 목록을 전달해야 하므로 Model을 파라미터로 지정하고, 이를 통해서 BoardServiceImpl 객체의 getList() 결과를 담아 전달한다. (addAttribute)
이제 테스트코드를 작성해서 테스트를 해봐야하는데 기존과 좀 다르게 작성할 것이다.
그 이유는 웹을 개발할 때 매번 URL을 테스트하기 위해서 Tomcat 과 같은 WAS를 실행하는 불편한 단계를 생략하기 위해서이다.
package org.zerock.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
//test for controller
@WebAppConfiguration
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
@Setter(onMethod_ = {@Autowired})
private WebApplicationContext ctx;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
@Test
public void testList() throws Exception {
log.info(
mockMvc.perform(MockMvcRequestBuilders.get("/board/list"))
.andReturn()
.getModelAndView()
.getModelMap()
);
}
}
|
cs |
테스트 클래스의 선언부에는 @WebAppConfiguration 어노테이션을 적용한다.
@WebAppConfiguration은 Servlet의 ServletContext를 이용하기 위해서인데, 스프링에서는 WebApplicationContext라는 존재를 이용하기 위해서 이다.
@Before 어노테이션이 적용된 setUp()에서는 import 할 때 JUnit을 이용해야 한다.
@Before가 적용된 메서드는 모든 테스트 전에 매번 실행되는 메서드가 된다.
MockMvc는 말 그대로 '가짜 mvc'라고 생각하면 된다. 가짜로 URL과 파라미터 등을 브라우저에서 사용하는 것처럼 만들어서 Controller 를 실행해 볼 수 있다. testList()는 MockMvcRequestBuilders라는 존재를 이용해서 GET 방식을 호출한다.
이후에는 BoardController의 getList()에서 반환된 결과를 이용해서 Model에 어떤 데이터들이 담겨 있는지 확인한다.
10.2.2 등록 처리와 테스트
BoardController에 POST 방식으로 처리되는 register()를 작성해보겠다.
@PostMapping("/register")
public String register(BoardVO board, RedirectAttributes rttr) {
log.info("register: " + board);
service.register(board);
rttr.addFlashAttribute("result", board.getBno());
return "redirect:/board/list";
}
|
cs |
register()메서드는 조금 다르게 String을 리턴 타입으로 지정하고, RedirectAttributes를 파라미터로 지정한다.
이는 등록 작업이 끝난 후 다시 목록 화면으로 이동하기 위함인데, 추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 RedirectAttributes를 이용한다.
리턴 시에는 'redirect:' 접두어를 사용하는데 이를 이용하면 스프링 MVC가 내부적으로 response.sendRedirect()를 처리해 주기 때문에 편리하다.
@Test
public void testRegister() throws Exception{
String resultPage = mockMvc.perform(MockMvcRequestBuilders.post("/board/register")
.param("title", "테스트 새글 제목")
.param("content", "테스트 새글 내용")
.param("writer", "user00")
).andReturn().getModelAndView().getViewName();
log.info(resultPage);
}
|
cs |
테스트할 때 MockMvcRequestBuilder의 post()를 이용하면 POST 방식으로 데이터를 전달할 수 있고, param()을 이용해서 전달해야 하는 파라미터들을 지정할 수 있다.
이러한 방식으로 코드를 작성하면 최초 작성 시에는 일이 많다고 느껴지나 매번 입력할 필욕 없기 때문에 오류가 발생하거나 수정하는 경우 반복적인 테스트가 수월해 진다.
실행되는 로그를 살펴보면 상단에 BoardVO 객체로 올바르게 데이터가 바인딩된 결과를 볼 수 있고, 중간에는 SQL의 실행 결과가 보인다. 마지막에는 최종 반환 문자열을 확인할 수 있다.
10.2.3 조회 처리와 테스트
등록 처리와 유사하게 조회 처리도 BoardController를 이용해서 처리할 수 있다.
특별한 경우가 아니라면 조회는 GET 방식으로 처리하므로, @GetMapping 을 이용한다.
@GetMapping("/get")
public void get(@RequestParam("bno") Long bno, Model model) {
log.info("/get");
model.addAttribute("board", service.get(bno));
}
|
cs |
BoardController의 get() 메서드에는 bno 값을 좀 더 명시적으로 처리하는 @RequestParam 을 이용해서 지정한다.(파라미터 이름과 변수 이름을 기준으로 동작하기 때문에 생략해도 무방하다.)
또한 화면 쪽으로 해당 번호의 게시물을 전달해야 하므로 Model을 파라미터로 지정한다.
@Test
public void testGet() throws Exception {
log.info(mockMvc.perform(MockMvcRequestBuilders
.get("/board/get")
.param("bno", "2"))
.andReturn()
.getModelAndView().getModelMap());
}
|
cs |
파라미터가 제대로 수집되었는지 확인하고 SQL의 처리결과를 확인할 수 있다.
10.2.4 수정 처리와 테스트
수정 작업은 등록과 유사하다. 변경된 내용을 수집해서 BoardVO 파라미터로 처리하고, BoardService를 호출한다.
수정 작업을 시작하는 화면의 경우에는 GET 방식으로 접근하지만 실제 작업은 POST 방식으로 동작하므로 @PostMapping 을 이용해서 처리한다.
@PostMapping("/modify")
public String modify(BoardVO board, RedirectAttributes rttr) {
log.info("modify:" + board);
if(service.modify(board)) {
rttr.addFlashAttribute("result", "success");
}
return "redirecte:/board/list";
}
|
cs |
service.modify()는 수정 여부를 boolean으로 처리하므로 이를 이용해서 성공한 경우에만 RedirecdtAttributes에 추가한다.
@Test
public void testModify() throws Exception {
String resultPage = mockMvc
.perform(MockMvcRequestBuilders.post("/board/modify")
.param("bno", "1")
.param("title", "수정된 테스트 새글 제목")
.param("content", "수정된 테스트 새글 내용")
.param("writer", "user00"))
.andReturn().getModelAndView().getViewName();
log.info(resultPage);
}
|
cs |
10.2.5 삭제 처리와 테스트
삭제 처리도 조회와 유사하게 작성한다. 삭제는 반드시 POST 방식으로만 처리한다.
@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, RedirectAttributes rttr) {
log.info("remove..." + bno);
if(service.remove(bno)) {
rttr.addFlashAttribute("result", "success");
}
return "redirect:/board/list";
}
|
cs |
BoardController의 remove()는 삭제 후 페이지의 이동이 필요하므로 RedirectAttributes를 파라미터로 사용하고 'redirect'를 이용해서 삭제 처리 후에 다시 목록 페이지로 이동한다.
@Test
public void testRemove()throws Exception{
//삭제 전 데이터베이스에 게시물 번호 확인할 것
String resultPage = mockMvc.perform(MockMvcRequestBuilders.post("/board/remove")
.param("bno", "23")
).andReturn().getModelAndView().getViewName();
log.info(resultPage);
}
|
cs |
MockMvc를 이용해서 파라미터를 전달할 때에는 문자열로만 처리해야 한다.
테스트 전에 게시물의 번호가 존재하는지 확인학 테스트를 실행한다.
출처 : 코드로 배우는 스프링 웹 프로젝트 [구멍가게 코딩단]
'Records > Spring Framework' 카테고리의 다른 글
오라클 데이터베이스 페이징 처리 (0) | 2021.03.05 |
---|---|
화면 처리 (0) | 2021.02.21 |
비즈니스 계층 (0) | 2021.02.16 |
영속/비즈니스 계층의 CRUD 구현 (0) | 2021.02.15 |
스프링 MVC 프로젝트의 기본 구성 (0) | 2021.02.15 |