[Spring] 스프링 입문 웹 개발 기초(13)

회원 서비스 테스트

테스트 코드 생성

spring45

spring46

만들었다면 이제 테스트 코드를 적어볼텐데 굳이 영어권 사람들과 프로젝트를 하는 것이 아니라면 테스트 코드는 빌드에 들어가지 않으므로 함수 이름을 한글로 적어줘도 된다.

join = 회원가입 이런 형식으로 바꿔줘도 된다는 뜻이다.

코드 형식

이제 회원가입을 만들어볼 것인데, 형식을 맞춰서 하는 것이 좋다.

given, when, then = “무엇인가 주어졌는데, 이걸 실행했을 때, 결과가 이게 나와야 해” 하는 형식이다.

하지만 그냥 절차대로 코딩한다고 이해하는게 넓은 범주로 보면 편할 것 같다. 저 의미에 국한되지 않고 이해하자.

회원가입 만들기

이제 회원가입을 만들어보자.

  1. 주어진 것 : 멤버를 새로 만들고 이름을 저장한다.
  2. 검증하고 싶은 것 : memberService로 member를 저장하고 리턴값으로 나오는 Id를 saveId에 저장한다.
  3. 검증 방법 : saveId로 memberService에 저장한 Name과 member의 Name이 같은지 검증한다.
MemberService memberService = new MemberService();

@Test
void 회원가입() {
    //given
    Member member = new Member();
    member.setName("hello");
    //when
    Long saveId = memberService.join(member);
    //then
    Member findMember = memberService.findOne(saveId).get();
    assertThat(member.getName()).isEqualTo(findMember.getName());
}

spring47

중복회원예외 테스트 (Try-catch)

하지만 이렇게 테스트 코드를 짠다면 너무 평범한 테스트 코드라고 할 수 있다.

테스트 코드를 사용할 때는 예외로 발생하는 부분을 테스트하는 것이 가장 중요하다.

그래서 중복회원예외의 경우를 테스트 해봐야한다.

try-catch문을 이용하여 중복회원을 테스트한다.

MemberService에서 join 메서드에 들어가있는 중복 회원 검증 기능은 validateDuplicateMember인데 IllegalStateException을 이미 존재하는 회원입니다.로 던지기에 catch에도 IllegalStateException으로 잡아준다.

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

spring48

중복회원테스트 (assertThrows)

하지만 try-catch문이 애매할 경우 assertThrows를 사용한다.

아까 사용했던 try-catch문은 잠시 주석처리해두고 짜보자.

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

코드를 설명하자면 오른쪽에 있는 람다식 로직을 태우면 왼쪽에 IllegalStateException예외가 터져야한다는 뜻이다.

나는 강의에서와 다르게 assertThrows가 바로 인식이 안되었는데, 그 이유는 import static org.junit.jupiter.api.Assertions.*assertThrows*; 를 import 해줘야했다.

만약 나처럼 되지 않는 사람들은 앞에 Assertions.assertThrows로 만들고 alt + enter를 이용하여 편하게 import를 해주는 것이 좋을 것이다.

그럼 이렇게 생각할 수 있다.

이전 try - catch문을 사용할 경우에는 문자열을 비교할 수 있었는데, assertThrows를 사용하면 불가능한가?

가능하다. Ctrl + Alt + V 를 눌러주면 자동으로 변수를 만들어준다는 꿀팁을 다시 사용하면 IllegalStateException으로 선언될 것이다.

그것을 비교해주면 된다.

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

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

spring49

이제 이전에 했던 테스트코드와 마찬가지로 clear를 해줘야한다.

MemoryMemberRepository memberRepository= new MemoryMemberRepository();

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

이제는 회원가입에 있던 setName의 hello를 spring으로 바꾸더라도 db가 모두 날라갔기에 전체 수행에 문제가 없다. 한번 spring으로 바꾸고 전체 코드를 실행하자.

spring50

Dependency Injection

여기서 근데 굳이 memberRepository 를 새로 만들어서 사용해야하냐는 의문점이 생긴다.

MemberService로 이동하자.

먼저 MemberService에 new 하고 있는 MemberRepository를 고쳐줘야한다.

그리고 나서 Alt + Insert를 통해 생성자를 만들어준다.

private final MemberRepository memberRepository;

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

이렇게 바뀌면 MemberService에서 직접 new 하지 않고 외부에서 memberRepository를 넣어주는 형식이 된다.

이런것을 우리는 의존성 주입(Dependency Injection)이라고 한다.

이제 다시 MemberServiceTest로 이동하자.

MemberService와 MemoryMemberRepository의 new를 제거해준다.

다음 @BeforeEach를 이용해 시작하기 전마다 실행하는 코드를 짜준다.

그 안에서 memberRepository의 new를 하고, memberService 안에 new 했던 memberRepository를 넣어준다.

MemberService memberService;
MemoryMemberRepository memberRepository;

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

최종코드

MemberServiceTest.java

import mess.hellospring.domain.Member;
import mess.hellospring.domain.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

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

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

    @Test
    void 회원가입() {
        //given
        Member member = new Member();
        member.setName("spring");
        //when
        Long saveId = memberService.join(member);
        //then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public 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
    }

    @Test
    void 회원찾기() {

    }

    @Test
    void findOne() {

    }
}

MemberService.java

import mess.hellospring.domain.Member;
import mess.hellospring.repository.MemberRepository;

import java.util.List;
import java.util.Optional;

public class MemberService {
    private final MemberRepository memberRepository;

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

    /**
     * 회원 가입
     */
    public Long join(Member member){
        validateDuplicateMember(member); //중복 회원 검증
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    /**
     * 전체 회원 조회
     */
    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberId){
        return memberRepository.findById(memberId);
    }
}

Leave a comment