[Spring] 슬라이스 테스트

Minit88·2023년 5월 1일
0

Spring

목록 보기
14/16
post-thumbnail

슬라이스 테스트란?

  • 단위 테스트의 경우 일반적으로 특정 모듈이나 계층, 기술에 의존적이지 않도록 작성하는 것이 좋다
  • 단위 테스트 만으로는 애플리케이션의 모든 기능이 정상적으로 동작한다라고 보장되지는 않는다.
  • 각각의 애플리케이션 계층 별로 잘 동작하는지 테스트를 진행한 후에 통합 테스트를 통해 테스트 작업이 마무리된다.
  • 개발자가 각 계층에 구현해 놓은 기능들이 잘 동작하는지 특정 계층만 잘라서 테스트하는 것을 슬라이스 테스트라고 한다.

API 계층 테스트

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

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

@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 검증 
    }
}

[코드] Controller 테스트용 테스트 클래스 기본 구조

  1. (1)의 @SpringBootTest 애너테이션은 Spring Boot 기반의 애플리케이션을 테스트하기 위한 Application Context를 생성한다.
  1. (2)의 @AutoConfigureMockMvc 애너테이션은 Controller 테스트를 위한 애플리케이션의 자동 구성 작업을 해준다.
  1. (3)에서 DI로 주입받은 MockMvc는 Tomcat 같은 서버를 실행하지 않고 Spring 기반 애플리케이션의 Controller를 테스트할 수 있는 완벽한 환경을 지원해 주는 일종의 Spring MVC 테스트 프레임워크이다.
  1. (4)와 같이 테스트하고자 하는 Controller 핸들러 메서드의 테스트 케이스를 작성하면 된다.
  1. Controller를 테스트하기 위해서는 (5)의 단계에서 테스트용 request body를 직접 만들어 주어야 한다. Given-When-Then 패턴에서 Given에 해당된다.
  1. (6)에서는 MockMvc 객체를 통해 요청 URI와 HTTP 메서드등을 지정하고, (5)에서 만든 테스트용 request body를 추가한 뒤에 request를 수행한다. Given-When-Then 패턴에서 When에 해당된다.
  1. (7)에서는 Controller에서 전달받은 HTTP Status와 response body 데이터를 통해 검증 작업을 한다.
    Given-When-Then 패턴에서 Then에 해당된다.

MemberController 테스트 - postMember

@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)
    }
}

[코드] MemberController의 postMember() 테스트

  1. Given
    • (1) 의 코드는 Given에 해당되며 Postman을 사용할 때 request body에 포함시키는 요청 데이터와 동일한 역할을 한다.
    • (2) 에서 Gson이라는 JSON 변환 라이브러리를 이용해서 (1)에서 생성한 MemberDto.Post 객체를 JSON 포맷으로 변환해준다.
  2. When
    • MockMvc로 테스트 대상 Controller의 핸들러 메서드에 요청을 전송하기 위해서는 기본적으로 (3)과 같이 perform() 메서드를 호출해야 하며 perform() 메서드 내부에 Controller 호출을 위한 세부적인 정보들이 포함된다.
    • (4) 에서 post() 메서드를 통해 HTTP POST METHOD와 request URL을 설정한다
    • (5) 에서 accept() 메서드를 통해 클라이언트 쪽에서 리턴 받을 응답 데이터 타입으로 JSON 타입을 설정한다
    • (6) 에서 contentType() 메서드를 통해 서버 쪽에서 처리 가능한 Content Type으로 JSON 타입을 설정한다
    • (7) 에서 content() 메서드를 통해 request body 데이터를 설정한다. request body에 전달하는 데이터는 (2) 에서 Gson 라이브러리를 이용해 변환된 JSON 문자열이다
  3. Then
    • MockMvc의 perform() 메서드는 ResultActions 타입의 객체를 리턴하는데, 이 ResultActions 객체를 이용해서 우리가 전송한 request에 대한 검증을 수행할 수 있다.
    • (8) 에서 andExpect() 메서드를 통해 파라미터로 입력한 매처로 예상되는 기대 결과를 검증할 수 있다.
    • (8) 에서 status().isCreated()를 통해 response status가 201(created)인지 매치시키고 있다. 즉, 백엔드 측에 리소스인 회우너 정보가 잘 생성되었는지를 검증한다.
    • (9) HTTP header에 추가된 Location의 문자열 값이 Location의 문자열 값이 "/v11/members/" 로 시작하는지 검증한다.

MemberController 테스트 - getMember

@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)
    }
}

[코드] MemberController의 getMember() 테스트

  1. Given
    • (1)의 코드 영역은 우리가 postMember()를 테스트할 떄의 코드와 동일한 코드이다.
    • (2)에서는 postMember()의 response에 전달되는 Location header 값을 가져오는 로직이다.
  2. When
    • (3)에서는 (2)에서 얻은 Location header의 값을 get(location)으로 전달한다.
  3. Then
    • (4)에서는 기대하는 HTTP status가 200 OK인지를 검증한다.
    • (5)에서 (7)까지는 getMember() 핸들러 메서드에서 리턴하는 response body의 각 프로퍼티의 값을 검증하는 기능
    • (5)에서는 jsonPath() 메서드를 통해 response body의 각 프로퍼티 중에서 응답으로 전달받는 email 값이 request body로 전송한 email과 일치하는지 검증하고 있다.
    • (6)에서는 jsonPath() 메서드를 통해 response body의 각 프로퍼티 중에서 응답으로 전달받는 name 값이 request body로 전송한 name과 일치하는지 검증한다.
    • (7)에서는 jsonPath() 메서드를 통해 response body의 각 프로퍼티 중에서 응답으로 전달받는 phone 값이 request body로 전송한 phone과 일치하는지 검증하고 있다.

데이터 액세스 계층 테스트

데이터 액세스 계층을 테스트하기 위한 한 가지 규칙

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

예를들어, test 클래스 내의 전체 테스트 케이스를 실행할 때 다음과 같은 순서로 테스트 케이스가 실행된다고 가정해보자.

  • testA() 실행
    • 테스트 데이터 한 건을 DB에 저장한다.
    • DB에 잘 저장되었는지 DB에서 조회하여 결과를 검증한다
  • testB() 실행
    • 특정 데이터가 DB에서 잘 조회되는지 기본키를 WHERE 조건으로 해서 DB에서 조회한다.
    • 만약 testA()에서 INSERT한 데이터의 기본키를 WHERE 조건으로 테스트하면 조회가 되므로 테스트 결과는 "passed"이다

실행 순서가 바뀔 경우, WHERE조건의 고정된 상태에서 DB에 조회를 했는데 원하는 결과 값이 없기 때문에 테스트 결과는 "failed"일 것이다.

이처럼 테스트 케이스는 여러 개의 테스트 케이스를 일괄적으로 실행시키더라도 각각의 테스트 케이스에 독립성이 보장되어야 한다.

이러한 문제를 해결하기 위해, DB의 상태를 테스트 케이스 실행 이전으로 되돌려서 깨끗하게 만들어야 한다.

MemberRepository 테스트 - 회원정보 저장

@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()));
    }
}

[코드[ MemberRepository의 데이터 저장 테스트 예

  1. Spring 데이터 액세스 계층을 테스트하기 위한 가장 핵심적인 방법은 (1)과 같이 @DataJpaTest 애너테이션이다.
    • @DataJpaTest 애너테이션을 테스트 클래스에 추가함으로써, MemberRepository의 기능을 정삭적으로 사용하기 위한 Configuration을 Spring이 자동으로 해준다.
    • @DataJpaTest 애너테이션은 @Transactional 애너테이션을 포함하고 있기 때문에 하나의 테스트 케이스 실행이 종료되는 시점에 데이터베이스에 저장된 데이터는 rollback 처리가 된다.
    • 즉, 여러 개의 테스트 케이스를 한꺼번에 실행시켜도 하나의 테스트 케이스가 종료될 때마다 데이터베이스의 상태가 초기 상태를 유지한다는 것이다.
  2. (2)에서 테스트 대상 클래스인 MemberRepository를 DI 받는다.
  3. (3)에서 테스트할 회원 정보 데이터를 준비한다.
  4. (4)에서 회원 정보를 저장한다.
  5. (5)에서 회원 정보가 잘 저장되었는지 검증한다.
    • 먼저 회원 정보를 정상적으로 저장한 뒤에 리턴 값으로 반환된 Member 객체가 null이 아닌지를 검증한다.
    • 나머지는 리턴 값으로 반환된 Member객체의 필드들이 테스트 데이터와 일치 하는지 검증한다.

회원 정보 조회 테스트

@DataJpaTest
public class MemberRepositoryTest {
    ...
		...

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

        // when 
        memberRepository.save(member);  // (2)
        Optional<Member> findMember = memberRepository.findByEmail(member.getEmail()); // (3)

				// then (4)
        assertTrue(findMember.isPresent()); // (4-1)
        assertTrue(findMember.get().getEmail().equals(member.getEmail())); // (4-2)
    }
}

[코드] MemberRepository의 데이터 조회 테스트

  • (1) 에서 테스트할 회원 정보 데이터를 준비한다.
  • (2) 에서 회원 정보를 저장한다.
  • (2) 에서 저장 후, 리턴되는 Member 객체를 이용하는 것이 아니라 (2)에서 저장한 회원 정보 중에서 이메일에 해당되는 회원 정보를 잘 조회하는지 테스트하기 위해 (3)과 같이 findByEmail()로 회원 정보를 조회하고 있다.
  • (4) 에서 회원 정보의 조회가 정상적으로 이루어지는지 검증한다
    • (4-1)과 같이 조회된 회원정보가 null이 아닌지를 검증한다
    • (4-2)에서 조회한 회원의 이메일 주소와 테스트 데이터의 이메일과 일치하는지 검증한다.
profile
" To be BE "

0개의 댓글