[Section3] Spring MVC - Testing 2

2023. 7. 2. 16:25

🧑🏻‍💻 TIL(Today I Learned)


✔️ Hamcrest, Slice Test

 

💡 Hamcrest

➡️ JUnit 기반의 단위 테스트에서 사용할 수 있는 Assertion Framework

 

🔎 사용하는 이유?

  • Assertion을 위한 매쳐(Matcher)가 자연스러운 문장으로 이어지므로 가독성이 향상됨
  • 테스트 실패 메세지를 이해하기 쉬움
  • 다양한  Matcher를 제공

➡️ 위와 같은 이유로 Assertion 메서드보다 더 많이 사용됨

 

 

🔎 Hamcrest Assertion 적용해 보기 

✔️ JUnit에서의 Assertion

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

public class HelloJunitTest {
    @DisplayName("Hello Junit Test")
    @Test
    public void assertionTest1() {
        String actual = "Hello, JUnit";
        String expected = "Hello, JUnit";

        assertEquals(expected, actual); // 📍
    }
}

 

✔️ Hamcrest에서의 Assertion

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

public class HelloHamcrestTest {

    @DisplayName("Hello Junit Test using hamcrest")
    @Test
    public void assertionTest1() {
        String expected = "Hello, JUnit";
        String actual = "Hello, JUnit";

        assertThat(actual, is(equalTo(expected)));  // 📍
    }
}

 

💡 Slice Test

➡️ 하나의 애플리케이션은 계층별로 역할이 있고 계층별로 서로 연동되기 때문에 각각의 계층 별로 잘 동작하는지 테스트를 진행한 후에 마지막으로 통합 테스트를 통해서 계층 간의 연동에 문제가 없는지 확인해야 함

➡️ 위처럼 개발자가 각 계층에 구현해 놓은 기능들이 잘 동작하는지 특정 계층만 잘라서 테스트하는 것

 

스모크 테스트(Smoke Test)
: QA 부서에서 본격적으로 전체적인 기능 테스트를 진행하기 전에 애플리케이션의 특정 수정 사항으로 인해 영향을 받을 수 있는 범위에 한해서 제한된 테스트를 진행하는 것 

 

🔎 API 계층 테스트

➡️ 테스트 대상은 Controller, Spring에서는 Controller를 테스트하기 위한 편리한 방법을 제공함

➡️ 테스트 클래스의 기본 구조는 아래와 같음

@SpringBootTest
@AutoConfigureMockMvc
public class ControllerTestDefaultStructure {
	
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    public void postMemberTest() {
    	//given --> 테스트용 request body 생성
        
        //when --> MockMvc 객체로 테스트 대상 Controller 호출
        
        //then --> Controller 핸들러 메서드에서 응답으로 수신한 HTTP Status 및 response body 검증
    }
}
  • @SpringBootTest
    : SpringBoot 기반의 애플리케이션을 테스트하기 위한  Application Context 생성(필요한 Bean 객체들이 등록되어 있음)
  • @AutoConfigureMockMvc
    : Controller 테스트를 위한 애플리케이션의 자동 구성 작업 해줌

  • @Autowired 로 주입받은 MockMvc
    : 톰캣과 같은 서버를 실행하지 않고 Spring 기반 애플리케이션의 Controller를 테스트할 수 있는 환경을 지원해 주는 일종의 Spring MVC 테스트 프레임워크
    : MockMvc 통해서 작성한 Controller를 호출해서 쉽게 Controller에 대한 테스트를 진행할 수 있음 

 

✔️ 실습 

@SpringBootTest
@AutoConfigureMockMvc
public class MemberControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private Gson gson;

    @Test
    void postMemberTest() throws Exception {
        //given --> 테스트용 requestbody 만들기
        MemberDto.Post post = new MemberDto.Post("hgd@gmail.com", "홍길동", "010-1111-2222");

        // Json 포맷으로 변환
        String content = gson.toJson(post);

        //when --> MockMvc 객체 통해 요청 URI와 HTTP 메서드 지정, 테스트용 requestbody 추가하여 request 수행
        ResultActions actions = mockMvc.perform(
                                        post("/v11/members")
                                                .accept(MediaType.APPLICATION_JSON)
                                                .contentType(MediaType.APPLICATION_JSON) 
                                                .content(content) 
        );

        //then --> responsebody 데이터를 통해 검증 작업 진행
        // 위에서 리턴한 ResultActions 타입의 객체를 이용해서 request에 대한 검증 수행 가능
        actions
                .andExpect(status().isCreated())
                .andExpect(header().string("Location", is(startsWith("/v11/members/"))));
                
                // HTTP header에 추가된 Location의 문자열 값이 "/v11/members/" 로 시작하는지 검증
                // Location header가 예상하는 값과 일치한다는 것은 백엔드 측에 회원 정보가 잘 생성되었다는 것을 의미함

    }
  1. given : 테스트용 requestbody 만들기 
  2. when : MockMvc 객체 통해 요청  URI와 HTTP 메서드 지정, 테스트용 requestbody 추가하여 request 수행
    ResultActions
    : Spring MVC 테스트 프레임워크에서 요청을 수행한 후 결과에 대한 추가 작업을 수행하기 위한 객체 
    : 요청에 대한 응답을 검증하고 기대값과 비교하는 등의 작업 수행할 수 있음 
    perform() 
    : MockMvc 객체를 사용하여 요청을 실행하고 응답에 대한 ResultActions 객체 반환
    post()
    : MockHttpRequestBuilders 가 MockHttpServletRequestBuilder 생성하기 위한 정적 팩토리 메서드 중 하나 
       (get(), patch(), delete() 등이 있음 )
    accept(), contentType(), content() 
    : MockHttpServletRequestBuilder 메서드
    :  각각 클라이언트에서 리턴 받을 응답 데이터 설정, 서버 쪽에서 처리 가능한 ContentType 설정, requestbody 데이터 설정
  3. then : responsebody 데이터를 통해 검증 작업 진행, 리턴한 ResultActions 타입의 객체 통해 request에 대한 검증 수행 가능
    → andExpect()
    : 응답 결과를 검증할 수 있는 메서드, 리턴한 객체가 제공함
    : andExpect()가 요구하는 ResultMatcher는 MockMvcResultMatchers에 정의된 정적 메서드 통해 생성 가능
    : MockMvcResultMatchers 객체는 컨트롤러가 어떤 결과를 전송했는지 서버의 응답 결과를 검증함

 

🔎 데이터 액세스 계층 테스트 

➡️ 데이터 액세스 계층 테스트 시에는 아래의 한 가지 규칙을 꼭 지켜야 함 

⭐️ DB의 상태를 테스트 케이스 실행 이전으로 되돌려서 깨끗하게 만든다.

 

public class DataAccessLayerTest {
		@Test
		public void testA() {
				// (1-1) 데이터가 DB에 잘 저장되는지를 테스트하기 위해 한 건의 데이터를 DB에 저장
				// (1-2) DB에 잘 저장되었는지 DB에서 조회해서 결과를 확인
		}

		@Test
		public void testB() {
				// (2-1) 데이터가 DB에서 잘 조회 되는지를 테스트하기 위해 DB에서 조회
		}
}
  • JUnit으로 작성한 테스트 케이스는 항상 일정한 순서로 테스트 케이스가 실행된다는 보장이 없음 
  • A -> B 순서대로 실행되면 "passed" 지만 다시 테스트 케이스를 시작했을 때 만약 test B() 가 먼저 실행된다면 결과는 "failed"
    => testA() 가 먼저 실행이 되지 않아 저장된 데이터가 없기 때문에 
  • 위처럼 테스트 케이스는 여러 개의 테스트 케이스를 일괄적으로 실행시키더라도 각각의 테스트 케이스에 독립성이 보장되어야 함 
  • 문제가 발생하지 않도록 하는 가장 좋은 방법은 테스트 케이스 하나가 실행될 때 해당 테스트 케이스에서 사용했던 데이터가 DB에 저장이 되어 있는 상태라면 테스트 케이스 실행 종료 시점에 저장되었던 데이터를 삭제해 주는 것 

 

 

BELATED ARTICLES

more