Spring Boot_회원 서비스 테스트

이세미·2023년 5월 12일
0

SpringBoot

목록 보기
11/23
post-thumbnail

Test Case 작성하기

이전에 테스트할 때는 직접 package를 만들고 클래스를 만들고 하였었는데,
훨씬 편하게 생성하는 단축키가 존재한다.

**단축키: command + shift + t

이 단축키를 사용하여 MemberServiceTest class를 자동 생성 하였다.

test - java - hellospring - service 밑에 MemberServiceTest 클래스가 자동 생성 되었다. 게다가 틀까지 모두 짜준다(ㄱㅇㄷ).

**참고) Test는 @Test void 회원가입() { } 처럼 한글로 이름을 작성해도 된다.

그리고 이제 내용을 채워넣을 차례이다.

먼저 MemberService memberService = new MemberService();를 만들었다.

회원가입을 하려면 먼저 member부터 만들어야 하는데,
이 때 추천하는 문법이 있는데

given - when - then 문법이다.

테스트 코드는 나누자면
무언가가 주어졌는데, 이를 실행했을 때, 무언가가 나와야 한다.
이 세 가지 단계로 구성된다.

테스트를 이 세 가지 단계로 나누어서 작성하면 테스트 코드가 길 때도
given을 보고 '아 이 데이터를 기반으로 하는구나'
when을 보고 '아 이걸 검증하는 거구나'
then을 보고 '여기가 검증부구나'를 알 수가 있다.
이 주석들을 깔고 코드를 짜면 훨씬 편하다.

일단 //given부분에

Member member = new Member();
member.setName("hello");

를 작성하고,

//when부분에 (검증할 내용 작성)

Long saveId = memberService.join(member);

를 작성하였다.

memberService의 join을 검증하는 것이기 때문에
memberService.join(member); 를 작성하고,
return은 저장한 Id가 나와야하므로
Long saveId = memberService.join(member);
로 코드를 수정한 것이다.

//then부분에는 (검증)

Member findMember = memberService.findOne(saveId).get();

assertThat(member.getName()).isEqualTo(findMember.getName());

를 작성하였다.

우선 내가 저장한 것이 repository에 있는 게 맞는지를 찾아보고 싶기 때문에 repository를 꺼내야 한다.

memberService.findOne(saveId);

saveId를 memberId로 넘기는 것이다.

return값이 Optional이기 때문에

Optional one = memberService.findOne(saveId);

로 코드를 수정해주고,

단순화하기 위해 바로 get()으로 받도록 코드를 수정해준다.

그래서 최종적으로

Member findMember = memberService.findOne(saveId).get();

이 된 것이다.

그리고 나서 member의 이름이 findMember의 이름과 같은지를 검증하기 위해서

Assertions.assertThat(member.getName()).isEqualTo(findMember.getName());

위 코드를 작성하였는데,

단축키: option + enter을 눌러 static import로 대체해주었다.

그래서 최종적으로

assertThat(member.getName()).isEqualTo(findMember.getName());

이 된 것이다.

실행했을 때 모두 잘 돌아가는 것이 확인되었다.

예외 플로우 확인하기

Test는 정상 플로우도 중요한데, 예외 플로우가 훨씬 더 중요하다.

join의 핵심은 저장이 되는 것도 중요하지만, 중복 회원 검증 logic을 잘 타서 예외가 잘 터뜨려지는가 를 확인하는 것도 중요하다.

이를 확인하기 위해서 중복 회원 예외가 실제로 발생하는 test code도 작성해 보았다.

@Test
  public void 중복_회원_예외(){
      //given
      Member member1 = new Member();
      member1.setName("spring");

      Member member2 = new Member();
      member2.setName("spring");
      //when
      memberService.join(member1);
      try {
          memberService.join(member2);
          fail();
      }catch (IllegalStateException e){
          assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
      }
      //then
  }

이 코드도 역시 given - when - then문법을 이용해서 작성하였다.
given 부분:

 Member member1 = new Member();
       member1.setName("spring");

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

member1에 spring이라는 이름을 저장하였고, member2에도 spring이라는 이름을 저장하였다.

when 부분:

memberService.join(member1); //여기까진 문제 없음
      try {
          memberService.join(member2);
			//두 번째 join부터 validateDuplicateMember에서 걸려서 
			예외가 터져야 하기 때문에 예외를 잡기 위해 try-catch문 안에 작성하였다.
          fail(); 
			//memberService.join(member2)줄에서 예외가 터지지 않고 
			그 다음줄로 내려가면 실패인 것이기 때문에 fail()이라고 적었다.
      }catch (IllegalStateException e){
          assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
			//예외 메시지가 MemberService 클래스의
			validateDuplicateMember에 작성한 에러 메시지와 동일한지 
			확인하는 코드를 작성하였다.
      }

성공! 잘 돌아간다.

또 다른 방법

이 경우에서 try catch문을 사용하는 것보다 더 좋은 문법이 존재한다.

assertThrows라는 문법이다.

try catch문을 모두 주석처리 하고, asserThrows를 사용한 다음 코드를 작성하였다.

assertThrows(IllegalStateException.class, () -> memberService.join(member2));

memberService.join(member2)를 실행할 때, IllegalStateException.class 예외가 터져야 한다는 뜻이다.

그럼 메시지는 어떻게 검증할까?

아까 작성했던
assertThrows(IllegalStateException.class, ()
->memberService.join(member2)); 코드에 단축키 command + option + v를 사용하여

IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));로 코드를 수정하였다.

그 후
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); 라는 메시지 검증 코드를 작성하였다.

회원가입의 setName을 spring으로 바꾸어봤더니, 제대로 돌아가지 않았다.
그 이유는 코드를 돌릴 때마다 join에서 database에 memory가 계속 쌓여서, name의 값이 spring인
값이 계속 누적이 되었기 때문이다.
spring은 이미 가입이 되어있는 것이다.

그래서 여기서도 clear을 시켜줘야 한다.

기존에는 MemberService memberService = new MemberService(); 밖에 안 되어 있었기 때문에

MemoryMemberRepository memberRepository = new MemoryMemberRepository();

를 추가하고, MemoryMemberRepositoryTest에 작성했던

@AfterEach
public void afterEach(){
memberRepository.clearStore();
}

AfterEach를 가져와서 새로 작성하였다.

--> 실행될 때마다 DB의 값을 clear해준다.

아까는 hello를 spring으로 바꿨을 때 제대로 동작하지 않았었는데, clear을 해준 지금은 제대로 동작하는 것을 볼 수 있다.

**참고) 단축키: control + r -> 이전에 실행했던 것을 재실행 해줌

그런데 여기서 문제가 하나 있다.

현재 MemberService 클래스에 있는
private final MemberRepository memberRepository = new MemoryMemberRepository();
코드의 MemoryMemberRepository와,

MemberServiceTest 클래스에 있는
MemoryMemberRepository memberRepository = new MemoryMemberRepository();
코드의 MemoryMemberRepository가

서로 다른 객체라는 것이다.

물론 현재는 MemoryMemberRepository 클래스의
private static Map<Long, Member> store = new HashMap<>(); 코드를
static으로 선언을 해놓았기 때문에 문제가 발생하지 않았지만,
static이 아니었다면 분명 문제가 발생했을 것이다.

이 뿐만이 아니라 그냥 원래 MemberService 클래스와 MemberServiceTest는 같은 Repository를 테스트 하는 것이 맞는 것이다.

그래서 해결 방법은, 둘이 같은 instance를 쓰게 만들어야 한다.

우선 MemberService 클래스에서

private final MemberRepository memberRepository = new MemoryMemberRepository();

private final MemberRepository memberRepository;

로 바꾸고,

constructor를 사용하여 memberRepository를 직접 new에서 생성하는 것이 아니라, 외부에서 생성하도록 바꿔준다.

public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

그리고 MemberServiceTest 클래스에 가서,

MemberService memberService = new MemberService();
MemoryMemberRepository memberRepository = new MemoryMemberRepository();

였던 코드에서,
MemberService를 생성할 때 MemoryMemberRepository를 직접 넣어주는 코드로 수정해준다.

@BeforeEach로, 동작하기 전에 넣어주는 코드를 작성한다.

class MemberServiceTest {

  MemberService memberService;
  MemoryMemberRepository memberRepository;

  @BeforeEach
  public void beforeEach(){
      memberRepository = new MemoryMemberRepository();
      memberService = new MemberService(memberRepository);
  }

beforeEach이므로, 각 테스트를 실행하기 전

memberRepository = new MemoryMemberRepository();

MemoryMemberRepository를 만들고, 그것을 memberRepository에 넣는다.

memberService = new MemberService(memberRepository);

그리고 MemberSevice를 만들어서 memberRepository를 넣으면, 같은 repository가 되는 것이다.

memberService입장에서는 직접 new하지 않고 외부에서 넣어주는 것인데, 이런 것을
Dependency Injection이라고 한다.(DI)

0개의 댓글