[SEB BE] Section 3. Hamcrest / 슬라이스 테스트

박두팔이·2023년 3월 7일
0

스프링프레임워크

목록 보기
13/18

Hamcrest?

JUnit 기반의 단위테스트에서 사용하는 Assertion(참이길 바라는) Framework이다.

Hamcrest를 사용하는이유?

  • Assertion을 위한 매쳐가 자연스러운 문장으로 이어져 가독성이 향상.
  • 테스트 실패 메시지를 이해하기 쉬움.
  • 다양한 matcher제공

API 계층테스트

슬라이스 테스트?

  • 단위테스트의 경우 특정 모듈이나 계층, 기술에 의존적이지 않도록 작성하는 것이 좋다.

  • 그러나 단위 테스트만으로는 모든 애플리케이션의 기능을 100% 정상적으로 잘 작동한다고 보장할 수 없다.

  • 따라서 슬라이스 테스트, 즉 계층별 테스트가 필요하다.

  • 애플리케이션은 계층별로 역할이 있다. 각각의 계층 별로 잘 동작하는지 테스트를 진행한 후 통합테스트를 통해 계층간 연동에 문제가 없는지 확인이 되면 테스트작업은 마무리 된다.

    개발자가 각 계층에 구현한 기능이 잘 작동하는지 특정 계층만 잘라서 테스트 하는 것이 바로 슬라이스 테스트이다.

  • 대부분 일정 상의 이유로 통합테스트는 QA부서에서 진행한다.

  • 본격적인 기능테스트를 하기 전에 제한된 테스트를 진행하는 것을 스모크 테스트(Smoke Test)라고 한다.


Controller 테스트를 위한 테스트 클래스 구조

  • 스프링 부트에서는 API계층의 컨트롤러를 테스트하기 위해 다양한 애너테이션을 제공한다.
  • 하나의 컨트롤러 클래스를 테스트하기 위한 코드는 다음과 같다.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest       // 1️⃣
@AutoConfigureMockMvc  // 2️⃣
public class ControllerTestDefaultStructure {
	
    // 3️⃣
    @Autowired
    private MockMvc mockMvc;
    
	// 4️⃣ 
    @Test
    public void postMemberTest() {
        
        // given 5️⃣ 
        //테스트용 request body 생성
        
        // when 6️⃣ 
        //MockMvc 객체로 테스트 대상 Controller 호출
        
        // then 7️⃣ 
        //Controller 핸들러 메서드에서 응답으로 수신한 HTTP Status 및 response body 검증 
    }
}

💡 코드설명 💡

1️⃣ @SpringBootTest

  • SpringBootTest애너테이션은 스프링 부트 기반의 애플리케이션을 테스트하기 위해 Application Context를 생성한다.
  • Application Context에는 애플리케이션에 필요한 Bean객체들이 등록되어 있다.

2️⃣ @AutoConfigureMockMvc

  • Controller테스트를 위한 애플리케이션의 구성을 자동으로 작업 해준다.

  • @AutoConfigureMockMvc 애너테이션을 추가해야 MockMvc 같은 기능을 사용할 수 있다.

3️⃣ @Autowired
private MockMvc mockMvc;

  • MockMvc는 서버실해애 없이 spring을 기반으로 Controller를 테스트 할 수 있는 환경을 지원해주는 Spirng MVC 테스트 프레임워크이다.

Controller 테스트 코드

ex) postMember() 테스트코드

package com.codestates.homework;

import com.codestates.member.dto.MemberDto;
import com.codestates.member.entity.Member;
import com.google.gson.Gson;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.transaction.annotation.Transactional;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; // 이 객체를 Importgodi endExpect()를 사용할 수 있음

@Transactional
@SpringBootTest
@AutoConfigureMockMvc
public class MemberControllerHomeworkTest {
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private Gson gson;

    @Test
    void postMemberTest() throws Exception {
        // given (1)
        MemberDto.Post post = new MemberDto.Post("hgd@gmail.com",
                "홍길동",
                "010-1234-5678");
        String content = gson.toJson(post);//(2) 제이슨형태로 변경


        // when
        ResultActions actions =
                mockMvc.perform(    //(3) controller의 핸들러 메서드에 요청을 전송하기 위한 perform() 호출
                        // (4)~(7): HTTP request에 대한 정보 MockMvcRequestBuilders 클래스를 이용해서 빌더패턴을 통해 request정보를 채워넣는다
                        post("/v11/members")//(4) post()를 통해 HTTP의 post 메서드와 request URL을 설정
                                .accept(MediaType.APPLICATION_JSON)//(5) accept()를 통해 클라이언트에게 리턴받을 응답방식으로 제이슨타입으로 설정
                                .contentType(MediaType.APPLICATION_JSON)//(6) contentType()메서드를 통해 서버에서도 "처리"할 수 있도록 제이슨 타입으로 설정
                                .content(content)//(7) content()를 통해 request body 데이터를 설정
                                                // (1)의 내용으로 (2)에서 Gson라이브러리를 이용해 변환된, json문자열이 담긴 content를
                                                // 컨트롤러가 클라이언트에게 요청했던 request body로 content로 전달해줌
                );

        // then
        // when에서 perform()를 통해 얻어낸 ResultActions타입의 객체를 이용하면 우리가 전송한 requst에 대한 검증을 수행할 수 있다.
        actions
                .andExpect(status().isCreated()) //(8) andExpect()를 통해 어쎄션 검증
                // andExpect()의 파라미터로 입력된 매쳐 status().isCreated()는 클라이언트의 request에 대한
                // ReSponse status가 201(created)인지 확인한다. 즉, 백엔드에 회원정보가 잘 생성되었는지 검증한다.

                .andExpect(header().string("Location", is(startsWith("/v11/members/"))));
                //(9) HTTP header에 추가된 Location의 문자열 값이 , "/v11/members/로 시작하는지 검증한다,
                // 헤더가 예상하는 값과 일치한다면 회원정보가 잘 생성되었다는 것이다.
    }

기억할 것!

@Transactional
@SpringBootTest
@AutoConfigureMockMvc
class MemberControllerTest {
.
.
}

👉🏻 이 세개의 애너테이션은 컨트롤러를 테스트 할 때 거의 한 set처럼 움직인다.

response body 응답 데이터에 포함된 한글이 깨질 경우

application.yml 파일에 설정을 추가한다.

...
...

server:
  servlet:
    encoding:
      force-response: true

위의 코드들이 완전한 슬라이스 테스트라고 할 수 있나?

여기까지의 코드로는 완전한 슬라이스 테스트라고 하기 어렵다.

왜냐하면 테스트에 집중해야 하는 계층은 api계층인데 서비스 계층이나 데이터 액세스 계층까지 불필요한 로직이 수행되고 있기 때문이다.

Mock(가짜) 객체를 사용하여 계층간의 연결을 끊어줌으로써 완전한 api계층 슬라이스 테스트를 진행할 수 있다.


핵심포인트!

  • 개발자가 각 계층에 구현해 놓은 기능들이 잘 동작하는지 계층별로 Slice하여 테스트 하는 것을 슬라이스 테스트라고 한다.

  • @SpringBootTest는 Spring Boot기반의 애플리케이션을 테스트하기 위한 Application Context를 생성한다.

  • @AutoConfigureMockMvc 애너테이션은 Contorller 테스트를 위해 애플리케이션을 자동으로 구성하는 작업을 한다.

  • MockMvc는 Tomcat같은 서버를 실행하지 않고도 Spring 기반의 컨트롤러를 테스트할 수 있는 완벽한 환경을 지원해주는 Spring MVC 테스트 프레임 워크이다.

  • MockMvc로 테스트 대상 컨트롤러의 핸들러 메서드에 요청을 전송하기 위해서는 기본적으로 perform()메서드를 먼저 호출해야 한다.

  • MockMvcRequestBuilders클래스를 이용해서 빌더 패턴을 통해 request정보를 채워 넣을 수 있다.

  • MockMvc의 perform() 메서드가 리턴하는 ResultActions 타입의 객체를 이용해서 request에 대한 검증을 수행할 수 있다.

profile
기억을 위한 기록 :>

0개의 댓글