[예약사이트] 3. 가게 추가

2020. 10. 22. 07:16프로젝트/etc

가게 추가 기능을 구현했습니다. 

프로그래밍적 지식은 테스트 할 때 사용할 수 있는 Mock object(가짜객체) - mockito라는 프레임워크와 HTTPie 라는 소프트웨어에 대해 알아보겠습니다. 

1) Review

이외에도 강의를 들으면서 어렴풋이 설계방식이나 객체 구현 흐름을 알 수 있었습니다. 가령 Controller를 먼저 생성하고 Service에 메서드를 만들어 준 다음 Repository에 인터페이스 메서드를 구현해주는 순서로 이루어진다는 점입니다. 또한 Controller와 Service 메서드가 생성되면 곧바로 테스트를 만들어 진행하는 점이 아직 익숙지 않은데요, 처음 보는 개념들이 많아 이 강의를 듣고 바로 TDD 개발을 진행시킬 순 없겠지만 배경지식을 차곡차곡 쌓아간다는 느낌으로 진행해보려구요. 

2) Mock object(가짜객체)

가짜 객체를 만들어 테스트에 사용하는 이유는 실제 객체들을 완벽히 구현하지 않아도 된다는 장점이 있기 때문입니다. Spring 프레임워크 내에선 Mock object의 mockito라는 프레임워크를 쓰면 됩니다. 사용방법은 먼저 @MockBean 아노테이션으로 가짜 객체를 선언해줍니다. 그 다음 테스트 하고 싶은 메서드를 생성한 다음 mockito API를 사용해 원하는 테스트를 진행하면 됩니다. 코드 보시죠.

 - 가게 목록 테스트 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RunWith(SpringRunner.class)
@WebMvcTest(RestaurantController.class)
public class RestaurantControllerTest {
 
    @Autowired
    private MockMvc mvc;
 
    @MockBean
    //가짜 객체
    private RestaurantService restaurantService;
 
    //가게 목록
    @Test
    //처리할 web에 대한 요청
    public void list() throws Exception {
        List<Restaurant> restaurants = new ArrayList<>();
        restaurants.add(new Restaurant(1004L, "JOKER House""Seoul"));
        //import mockito given 메서드 - 가짜 객체
        given(restaurantService.getRestaurants()).willReturn(restaurants);
 
        mvc.perform(get("/restaurants"))
                .andExpect(status().isOk())
                .andExpect(content().string(
                        containsString("\"id\":1004")
                ))
                .andExpect(content().string(
                        containsString("\"name\":\"JOKER House\"")
                ));
 
    }
cs

테스트하고 싶은 메서드는 restaurantService의 getRestaurants() 입니다. 이를 위해 데이터 타입이 Restaurant 객체인  restaurant 리스트 배열을 선언해 가게를 주입하는 작업이 16~17행에 이루어졌습니다.

given 메서드는 mockito API입니다. 괄호 안의 메서드를 실행하면 restaurants 리스트 인스턴스를 willReturn = 반환할 것이다. 의 명령으로 코드가 실행됩니다. 

mvc.perform은 가짜 객체와 연관되있진 않지만 HTTP가 제대로 정보를 전달해주고 view로 구현되는지를 보는 MockMvc API입니다. URI "/restaurants"를 요청할 때 아래 기대조건을 충족시키는 지 테스트하는 코드입니다.

 - 가게 추가 테스트 코드

가게 추가는 클라이언트에게 반환하는 정보가 POST 방식으로 이루어집니다. GET 방식과 차이점은 정보가 노출되지 않는 다는 점입니다. 따라서 추가한 가게가 DB 테이블에 업로드 되는 식의 view는 클라이언트에게 노출되지 말아야하기 때문에 POST를 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
    @Test
    public void create() throws Exception {
        mvc.perform(post("/restaurants")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\":\"BeRyong\":\"address\":\"Busan\"}"))
                .andExpect(status().isCreated())
                .andExpect(header().string("location""/restaurants/1234"))
                .andExpect(content().string("{}"));
 
        verify(restaurantService).addRestaurant(any()); //무엇이든 넣어도 완성되는 any mockito
    }
cs

3행에 보시면 가게 목록 테스트 코드와 달리 post 메서드가 쓰였습니다. 4행 contentType은 정보가 JSON 형식으로 전달될 거라는 명시이며 전달되는 세부정보는 5행과 같습니다. 

7행의 header는 서버요청/응답할 때 전송하는 HTTP 양식 중 한 부분입니다. 서버나 파일 형식, 쿠키와 같은 웹 관련 정보를 전달하는 메시지 입니다. header의 "location"이 "/restaurants/1234"가 맞게 작성되어 있는지 확인하는 부분이며 8행도 마찬가지도 response body 즉 실제 내용 content 부분이 {}로 비워져있는지 확인합니다. 아직 Service 객체를 만들기 전이라 정보를 입력하지 않았기 때문에 error가 뜹니다.

마지막 10행의 verify() 메서드는 mockito API이며 구문이 한번이라도 호출되었는지를 체크합니다. 여기선 addRestaurant 메서드의 호출을 확인하고 있습니다. any()를 사용한 것은 어떤 값이 들어가든 호출만 된다면 no matter라는 의미입니다. 

3) 가게 목록 Controller 코드

코드 그대로 입니다. 아직 application package의 Service 객체 완성 전이라 수정될 가능성이 큽니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
    @PostMapping("/restaurants")
    public ResponseEntity<?> create(@RequestBody Restaurant resource)
            throws URISyntaxException {
        //@RequestBody JSON 형식으로 넘겨준 형식을 받을 수 있는 클래스를 만들어 줄것임
        String name = resource.getName();
        String address = resource.getAddress();
 
        Restaurant restaurant = new Restaurant(1234L, name, address);
        restaurantService.addRestaurant(restaurant);
 
        URI location = new URI("/restaurants/" + restaurant.getId());
        return ResponseEntity.created(location).body("{}");
    }
cs