04 회원 도메인 개발 - Member 기능 테스트

shin·2023년 9월 10일
0

1. Member Test Code

JUnit4 사용

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class MemberServiceTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    public void 회원가입() throws Exception {
        //given
        Member member = new Member();
        member.setName("kim");

        //when
        Long saveId = memberService.join(member);

        //then
        assertEquals(member, memberRepository.findOne(saveId));
    }

    @Test(expected = IllegalStateException.class)
    public void 중복_회원_예외() throws Exception {
        //given
        Member member1 = new Member();
        member1.setName("kim");

        Member member2 = new Member();
        member2.setName("kim");

        //when
        memberService.join(member1);
        memberService.join(member2);

        //then
        fail("중복 회원 가입 예외 발생해야 한다.");
    }

}

@RunWith(SpringRunner.class)

  • Spring과 테스트 통합
  • JUnit에서 Spring을 같이 엮어서 사용할 때, SpringRunner 클래스를 @RunWith 어노테이션 옵션으로 지정함

@SpringBootTest

  • SpringBoot를 띄운 상태로 테스트하기 위함
  • 스프링 컨테이너 안에서 테스트를 돌리기 위함
  • 해당 어노테이션을 지정해놓지 않으면 @Autowired 전부 다 실패해서 MemberServiceMemberRepoistory를 가져올 수 없음

@Transactional

  • 반복 가능한 테스트 지원
  • 각각의 테스트를 실행할 때마다 트랜잭션을 시작하고 테스트가 끝나면 트랜잭션을 강제로 롤백함
  • 이 어노테이션은 테스트 케이스에서 사용될 때만 롤백됨
    • 서비스나 리포지토리 클래스에서 해당 어노테이션을 붙인다고 해서 롤백되지 않음

테스트 시 주의 사항

  • H2 데이터베이스를 켜놓은 상태에서 테스트를 수행해야 함
  • @Transactional 선언 필수

1) 회원가입 테스트

@Test
    public void 회원가입() throws Exception {
        //given
        Member member = new Member();
        member.setName("kim");

        //when
        Long saveId = memberService.join(member);

        //then
        assertEquals(member, memberRepository.findOne(saveId));
    }
  • JPA에서 같은 트랜잭션 안에서 pk(id)값이 똑같으면 같은 영속성 컨텍스트 하나로 관리됨

테스트 시 select 쿼리만 실행되고, insert 쿼리는 실행되지 않는 이유

  • save 메서드는 EntityManagerpersist
  • persist를 한다고 해서 DB에 쿼리문이 나가진 않음
  • DB transaction이 commit될 때, flush 되면서 insert 쿼리가 나감

insert 쿼리를 수행하고 싶다면 (실제 트랜잭션 rollback X)

    @Autowired EntityManager em;

    @Test
    @Rollback(value = false)
    public void 회원가입() throws Exception {
        //given
        Member member = new Member();
        member.setName("kim");

        //when
        Long saveId = memberService.join(member);

        //then
        em.flush();
        assertEquals(member, memberRepository.findOne(saveId));
    }

  • JPA @Transactional은 기본적으로 RollBack을 수행하기 때문에
  • test 메서드 수행 시 테스트 결과를 그대로 DB에 저장하고 싶다면, 메서드 위에 @Rollback(value = false)를 추가해야 함

insert 쿼리가 수행되지만, 실제 트랜잭션은 rollback하고 싶다면

    @Autowired EntityManager em;

    @Test
    public void 회원가입() throws Exception {
        //given
        Member member = new Member();
        member.setName("kim");

        //when
        Long saveId = memberService.join(member);

        //then
        em.flush();
        assertEquals(member, memberRepository.findOne(saveId));
    }
  • em.flush()로 디비에 영속성컨텍스트에 있는 쿼리를 날리고, 테스트가 끝날때 @Transactionalrollback을 수행함

사실 테스트는 디비에 데이터가 남으면 안되는 것이기 때문에 첫번째 방법으로 작성해야함

  • 하지만 디비에 제대로 들어가고 있는지 확인은 필요함
  • 작은 메모리 DB를 띄워서 테스트용으로 사용하는 것이 좋음(뒤에 자세히 언급)

2) 중복 회원 예외처리 테스트

  • 예외처리 테스트가 성공하면 그대로 return이 되어서 테스트 성공
  • 테스트가 실패하면 fail이 수행됟어 예외 발생이 실패했음을 알려야 함

  • 따라서 이미 존재하는 회원일 때 발생하는 IllegalStateException를 잡아서 처리해줘야 함
@Test
    public void 중복_회원_예외() throws Exception {
        //given
        Member member1 = new Member();
        member1.setName("kim");

        Member member2 = new Member();
        member2.setName("kim");

        //when
        memberService.join(member1);
        try {
            memberService.join(member2);
        } catch (IllegalArgumentException e){
            return;
        }

        //then
        fail("중복 회원 가입 예외 발생해야 한다.");
    }
  • 만약 위 테스트에서 member2의 Name을 다르게 설정하여 중복된 회원 가입이 아닌 경우를 만들어서 테스트를 수행할 경우
  • 위에 코드에서 정의한 fail 즉, JUnit에서 제공하는 AssertionError가 발생함
    • AssertionError를 발생시켜서 테스트 코드를 제대로 작성하지 않은 경우에 확인이 가능해짐
	@Test(expected = IllegalStateException.class)
    public void 중복_회원_예외() throws Exception {
        //given
        Member member1 = new Member();
        member1.setName("kim");

        Member member2 = new Member();
        member2.setName("kim");

        //when
        memberService.join(member1);
        memberService.join(member2);

        //then
        fail("중복 회원 가입 예외 발생해야 한다.");
    }
  • 추가로try-catch 문을 사용하여 예외처리를 하는 것 대신, @Test 어노테이션에 expected 옵션에 예외처리할 예외 클래스를 지정해주면, 더 간단한 코드로 예외처리가 가능함

3) 메모리 DB 사용

  • 테스트는 격리된 환경에서 실행하고 끝나면 데이터를 초기화하는 것이 좋기 때문에 메모리 DB를 사용하는 것이 이상적임
  • 테스트 케이스를 위한 스프링 환경과 일반적으로 애플리케이션을 실행하는 환경은 다르기 때문에 설정 파일을 다르게 사용함

test/resources/application.yml

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    username: sa
    password:
    driver-class-name: org.h2.Driver

  jpa:
   hibernate:
    ddl-auto: create
   properties:
    hibernate:
#      show_sql: true
      format_sql: true

logging.level:
  org.hibernate.SQL: debug
  org.hibernate.type: trace
  • 테스트를 수행할 때 test/resources/application.yml 설정 파일을 읽음
    • 만약에 이 위치에 설정 파일이 존재하지 않으면 src/resources/application.yml을 읽음
spring:
#  datasource:
#    url: jdbc:h2:mem:testdb
#    username: sa
#    password:
#    driver-class-name: org.h2.Driver
#
#  jpa:
#   hibernate:
#    ddl-auto: create-drop
#   properties:
#    hibernate:
#      show_sql: true
#      format_sql: true

logging.level:
  org.hibernate.SQL: debug
#  org.hibernate.type: trace
  • 그러나 스프링 부트는 별도의 테스트 datasource 설정이 없으면 기본적으로 메모리 DB를 사용하기 때문에 위 코드처럼 log 설정 부분을 제외하고는 추가 설정이 필요가 없음
  • driver-class 또한 현재 등록된 라이브러리를 보고 찾아줌
  • 그리고 ddl-auto 또한 create가 아닌 create-drop 모드로 동작하여, table create 후에 drop까지 이루어져서 데이터가 자동 초기화되도록 해줌
  • 따라서 데이터 소스나 JPA 관련 별도의 추가 설정을 하지 않아도 됨


테스트 코드 작성법 참고 : https://martinfowler.com/bliki/GivenWhenThen.html

강의 : 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발

profile
Backend development

0개의 댓글