회원 서비스 테스트

<aside> 🌟 [TIP]

인텔리제이에서 Command + Shift + T 하면 Create New Test...이 나옵니다.

Untitled

클릭하면 아래의 창이 나옵니다.

Untitled

Junit5로 설정을 하고, Class name은 현재의 클래스의 이름에 Test를 붙여서 자동완성 되어 있습니다.

그리고 Method에 체크표시를 하면 각 메서드에 대해서 @Test를 만들어줍니다.

Untitled

</aside>

<aside> ❗ 테스트는 과감하게 한글 이름으로 바꿔도 됩니다.

실제 동작하는 코드들은 한글로 이름을 적기가 애매한데 테스트는 한글로도 많이 적습니다. 바로바로 직관적으로 쉽게 알아들을 수 있기 때문입니다.

그리고 빌드될 때 테스트 코드는 실제 코드에 포함되지 않습니다.

</aside>

테스트를 만들었으니 하나씩 채워나가 봅시다. 먼저 회원 가입이 잘 되는지 보겠습니다.

class MemberServiceTest {

    MemberService memberService = new MemberService(); //...1

    @Test
    void 회원가입() {
			  //...2
        //given
        Member member = new Member();
        member.setName("hello");

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

        //then
				//...3
        Member findMember = memberService.findOne(saveId).get(); 
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

...
  1. 회원 가입을 하려면 먼저 Service가 있어야 합니다.

  2. 회원 가입을 하려면 먼저 맴버를 만들어야 합니다.

    그런데 여기서 추천하는 문법이 있습니다. 바로 given - when - then 문법입니다.

    1. given

      어떤 환경이 주어졌을 때

    2. when

      이렇게 실행 했을 때

    3. then

      결과가 이렇게 된다.

    대부분 테스트는 given - when - then으로 짤립니다.

    또 이렇게 해놓으면 테스트가 길 때 given을 보면 "이 데이터를 기반으로 하는구나", when을 보면 "이걸 검증하는구나", then을 보면 "여기가 검증부구나"를 알 수 있습니다.

    주석을 적어 놓는 것만으로 많은 도움이 됩니다.

    물론 안맞는 경우도 있습니다. 하지만 처음 할 때는 given - when - then의 틀에 맞춰서 작성하는 것을 추천드립니다. 이 패턴을 숙지하고 점점 더 상황에 맞게 변형시키는 것이 좋습니다.

  3. memberService.findOne(saveId).get()으로 리포지토리에 저장한 회원을 가져오고 가져온 회원인 findMember의 이름과 회원 가입한 member의 이름이 같은지 확인합니다.

이제 실행을 하면 당연히 통과합니다.

Untitled

그런데 여기서 중요한게 있습니다. 위의 테스트는 너무 간단합니다.

테스트는 정상 플로우도 중요하지만 예외 플로우가 훨씬 더 중요합니다.

위 테스트는 사실 반쪽짜리 테스트입니다. join의 핵심은 저장이 되는 것도 중요하지만 중복 로직을 잘 타서 예외가 터트려지는지 확인도 해야합니다.

테스트를 추가하겠습니다.

...

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

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

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

/*        try {
            memberService.join(member2);
            fail();
        } catch (IllegalStateException e) {
            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        }
*/

        //then
    }

...
  1. 같은 이름의 회원을 생성했습니다.

  2. 그리고 join을 하는데 처음에는 문제가 없겠지만 두 번째 join은 validateDuplicateMember에 걸려서 예외가 터져야합니다.

    1. 예외가 터진 것을 잡으려면 try catch로 잡을 수도 있습니다.

              try {
                  memberService.join(member2);
                  fail();
              } catch (IllegalStateException e) {
                  assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
              }
      
      1. 실행이 된다음에 Exception이 안터지고 밑으로 내려가면 실패니까 fail()이라고 해줍니다.
      2. Exception 터지면 catch부로 올테니 정상적으로 성공을 한 것입니다.
        • 예외 메세지가 "이미 존재하는 회원입니다."와 같다면 검증 성공입니다.

      실행을 해봅니다.

      Untitled

      혹시 모르니 다른 문장을 적어서 실행을 해보겠습니다.

      Untitled

      하지만 이 것 때문에 try catch를 넣는게 되게 애매합니다. 그래서 좋은 문법을 제공합니다.

    2. assertThrows

      assertThrows 자세히

      assertThrows(IllegalStateException.class, () -> memberService.join(member2));
      
      1. () -> memberService.join(member2) 로직을 실행하면 IllegalStateException 예외가 터져야합니다.

      실행을 해봅니다.

      Untitled

      하지만 예외를 바꿔서 실행하면 에러가 터집니다. NullPointerException으로 바꿔서 실행해봅니다.

      Untitled

      그리고 더 좋은게 assertThrows는 메세지 반환이 됩니다.

      IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
              
      assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
      

      실행을 해봅니다.

      Untitled

그런데 만약 회원가입() 메서드에서 member 이름을 "spring"이라고 받는다면 테스트가 실패할 것 입니다.