슬라이스 테스트(Slice Test)

Backend kwon·2023년 10월 12일
0

⚫ 슬라이스 테스트란?

하나의 애플리케이션은 계층별로 역할이 있고, 계층별로 서로 연동되기 때문에 각각의 계층 별로 잘 동작하는지 테스트를 진행한 후에 마지막으로 통합 테스트를 통해서 계층 간의 연동에 문제가 없는지 확인해야 비로소 개발자의 테스트 작업이 마무리되는 것이라고 할 수 있습니다.

개발자가 각 계층에 구현해 놓은 기능들이 잘 동작하는지 특정 계층만 잘라서(Slice) 테스트하는 것을 슬라이스 테스트(Slice Test)라고 합니다.

 

⚫ API 계층 테스트

API 계층의 테스트 대상은 대부분 클라이언트의 요청을 받아들이는 핸들러인 Controller입니다.

  • Controller 테스트를 위한 테스트 클래스 구조
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 애너테이션은 Spring Boot 기반의 애플리케이션을 테스트하기 위한 Application Context를 생성합니다.
Application Context에는 애플리케이션에 필요한 Bean 객체들이 등록되어 있습니다.

(2)의 @AutoConfigureMockMvc 애너테이션은 Controller 테스트를 위한 애플리케이션의 자동 구성 작업을 해줍니다.
Spring Boot의 자동 구성을 통해 애플리케이션의 설정을 손쉽게 사용하듯이 @AutoConfigureMockMvc 애너테이션을 추가함으로써 테스트에 필요한 애플리케이션의 구성이 자동으로 진행됩니다.

(3)의 MockMvc 같은 기능을 사용하기 위해서는 @AutoConfigureMockMvc 애너테이션을 반드시 추가해 주어야 합니다.
(3)에서 DI로 주입받은 MockMvc는 Tomcat 같은 서버를 실행하지 않고 Spring 기반 애플리케이션의 Controller를 테스트할 수 있는 완벽한 환경을 지원해 주는 일종의 Spring MVC 테스트 프레임워크입니다.

MockMvc 객체를 통해 우리가 작성한 Controller를 호출해서 손쉽게 Controller에 대한 테스트를 진행할 수 있습니다.

이로써 Controller를 테스트할 준비가 되었습니다.

이제 (4)와 같이 테스트하고자 하는 Controller 핸들러 메서드의 테스트 케이스를 작성하면 됩니다.

Controller를 테스트하기 위해서는 (5)의 단계에서 테스트용 request body를 직접 만들어 주어야 합니다.
Given-When-Then 패턴에서 Given에 해당됩니다.

(6)에서는 MockMvc 객체를 통해 요청 URI와 HTTP 메서드등을 지정하고, (5)에서 만든 테스트용 request body를 추가한 뒤에 request를 수행합니다.
Given-When-Then 패턴에서 When에 해당됩니다.

(7)에서는 Controller에서 전달받은 HTTP Status와 response body 데이터를 통해 검증 작업을 진행합니다.
Given-When-Then 패턴에서 Then에 해당됩니다.

 

MemberController 테스트

  • HTTP Post request에 대한 테스트
import com.Member.member.dto.MemberDto;
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.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.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class MemberControllerTest {
    @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)
	                                post("/v11/members")  // (4)
                                        .accept(MediaType.APPLICATION_JSON) // (5)
                                        .contentType(MediaType.APPLICATION_JSON) // (6)
                                        .content(content)   // (7)
                                );

        // then
        actions
                .andExpect(status().isCreated()) // (8)
                .andExpect(header().string("Location", is(startsWith("/v11/members/"))));  // (9)
    }
}

(참고) Gson 라이브러리를 사용하기 위해서는 build.gradle의 dependencies {...}에 implementation 'com.google.code.gson:gson'를 추가해 주어야 합니다.

(When 부분)
post() 메서드를 통해 HTTP POST METHOD와 request URL을 설정합니다.
accept() 메서드를 통해 클라이언트 쪽에서 리턴 받을 응답 데이터 타입으로 JSON 타입을 설정합니다.
contentType() 메서드를 통해 서버 쪽에서 처리 가능한 Content Type으로 JSON 타입을 설정합니다.
content() 메서드를 통해 request body 데이터를 설정합니다.
request body에 전달하는 데이터는 Gson 라이브러리를 이용해 변환된 JSON 문자열입니다.

(then 부분)
MockMvc의 perform() 메서드는 ResultActions 타입의 객체를 리턴하는데, 이 ResultActions 객체를 이용해서 우리가 전송한 request에 대한 검증을 수행할 수 있습니다.

andExpect() 메서드를 통해 파라미터로 입력한 매처(Matcher)로 예상되는 기대 결과를 검증할 수 있습니다.
status().isCreated()를 통해 response status가 201(Created)인지 매치시키고 있습니다. 즉, 백엔드 측에 리소스인 회원 정보가 잘 생성(저장)되었는지를 검증합니다.
header().string("Location", is(startsWith("/v11/members/")))을 통해 HTTP header에 추가된 Location의 문자열 값이 “/v11/members/”로 시작하는지 검증합니다.
Location header가 예상하는 값과 일치한다라는 것은 백엔드 측에 리소스(회원 정보)가 잘 생성되었다는 것을 의미합니다.

 

  • HTTP GET request에 대한 테스트
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.ResultActions;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

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.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@Transactional
@SpringBootTest
@AutoConfigureMockMvc
class MemberControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private Gson gson;

    ...
    ...

    @Test
    void getMemberTest() throws Exception {
        // =================================== (1) postMember()를 이용한 테스트 데이터 생성 시작
        // given
        MemberDto.Post post = new MemberDto.Post("hgd@gmail.com","홍길동","010-1111-1111");
        String postContent = gson.toJson(post);

        ResultActions postActions =
                mockMvc.perform(
                        post("/v11/members")
                                .accept(MediaType.APPLICATION_JSON)
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(postContent)
                );
        // =================================== (1) postMember()를 이용한 테스트 데이터 생성 끝

        // (2)
        String location = postActions.andReturn().getResponse().getHeader("Location"); // "/v11/members/1"

        // when / then
        mockMvc.perform(
                        get(location)      // (3)
                                .accept(MediaType.APPLICATION_JSON)
                )
                .andExpect(status().isOk())    // (4)
                .andExpect(jsonPath("$.data.email").value(post.getEmail()))   // (5)
                .andExpect(jsonPath("$.data.name").value(post.getName()))     // (6)
                .andExpect(jsonPath("$.data.phone").value(post.getPhone()));  // (7)
    }
}

(2)에서는 postMember()의 response에 전달되는 Location header 값을 가져오는 로직입니다.
(2)와 같이 postActions.andReturn().getResponse().getHeader("Location")로 접근해서 Location header의 값을 얻어올 수 있습니다.

(5)에서는 jsonPath() 메서드를 통해 response body(JSON 형식)의 각 프로퍼티 중에서 응답으로 전달받는 email 값이 request body로 전송한 email과 일치하는지 검증하고 있습니다.

MockMvcResultMatchers 클래스에서 지원하는 jsonPath()를 사용하면 JSON 형식의 개별 프로퍼티에 손쉽게 접근할 수 있습니다.

 

⚫ 데이터 엑세스 계층 테스트

  • 회원 정보 저장 테스트
mport com.Member.member.entity.Member;
import com.Member.member.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import static org.junit.jupiter.api.Assertions.*;

@DataJpaTest   // (1)
public class MemberRepositoryTest {
    @Autowired
    private MemberRepository memberRepository;   // (2)

    @Test
    public void saveMemberTest() {
        // given  (3)
        Member member = new Member();
        member.setEmail("hgd@gmail.com");
        member.setName("홍길동");
        member.setPhone("010-1111-2222");

        // when  (4)
        Member savedMember = memberRepository.save(member);

        // then  (5)
        assertNotNull(savedMember); // (5-1)
        assertTrue(member.getEmail().equals(savedMember.getEmail()));
        assertTrue(member.getName().equals(savedMember.getName()));
        assertTrue(member.getPhone().equals(savedMember.getPhone()));
    }
}

spring에서 데이터 액세스 계층을 테스트하기 위한 가장 핵심적인 방법은 바로 @DataJpaTest 애너테이션입니다.

@DataJpaTest 애너테이션을 테스트 클래스에 추가함으로써, MemberRepository의 기능을 정상적으로 사용하기 위한 Configuration을 Spring이 자동으로 해주게 됩니다.

@DataJpaTest 애너테이션은 @Transactional 애너테이션을 포함하고 있기 때문에 하나의 테스트 케이스 실행이 종료되는 시점에 데이터베이스에 저장된 데이터는 rollback 처리됩니다.

즉, 여러 개의 테스트 케이스를 한꺼번에 실행시켜도 하나의 테스트 케이스가 종료될 때마다 데이터베이스의 상태가 초기 상태를 유지한다는 것입니다.

 

<참고>

위는 Spring Data JPA 환경에서의 애너테이션이지만,
Spring JDBC 환경에서는 @JdbcTest, Spring Data JDBC 환경에서는 @DataJdbcTest를 사용하면 손쉽게 데이터 액세스 계층에 대한 테스트를 진행할 수 있습니다.

profile
백엔드개발자를 향해서

0개의 댓글