Notice
Recent Posts
Recent Comments
관리 메뉴

Developer Gonie

[스프링 부트] 13. 회원 서비스 테스트 본문

인프런 김영한님 강의/1 . 스프링 부트 입문

[스프링 부트] 13. 회원 서비스 테스트

이대곤 2022. 6. 26. 22:42

이번 게시물 흐름 요약

service 패키지 위치에서 생성한 MemberService 클래스 내의 메서드 들을 테스트 한다.

특히 join (회원가입) 메소드에서 중복 이름을 가진 회원을 등록하려는 경우 예외가 발생 하는지,

그리고 그 예외에서 발생시킨 에러 메세지가 예측한 것과 같은지를 체크함

테스트 코드 관련해서 추가적으로 알려주신 부분

* 테스트 코드 기본틀을 자동으로 생성하는 방법 

클래스 내부 - 오른쪽 마우스 - Generate... - Test..., Testing library는 JUnit5 선택, 테스트 코드 작성할 메소드 선택 후 OK

이러면 내가 지정한 클래스의 폴더구조까지 같게 테스트 클래스가 생성된다.

 

* 테스트 메소드의 이름은 과감하게 한글로 바꿔도 된다.

실제 프로덕션 코드에 대해서는 메서드 혹은 변수에 한글로 이름을 적기가 애매한데
테스트 코드에서는 영어권 사람하고 일하는게 아니면 직관적으로 쉽게 알 수 있기 위해 한글로도 많이 적는다고 한다.

또한, 테스트 코드들은 프로젝트를 빌드시켰을 때 여기에 포함되지 않는다고 한다.

 

* 테스트 메소드의 작성시 유용한 given, when, then 주석틀

테스트 코드 작성시 아래와 같이 주석을 적어두고 시작하는 방법을 추천하셨다.
코드 작성시에 뭘 해야하는 지 알기 쉽고, 코드가 길때 이해하기도 쉽다.
물론 이 패턴에 맞지 않는 경우도 있는데 시작을 이렇게 해보라는 것이지 나중엔 실력에 맞게 변형해 작성할 수 있게 될 것이다.

// given : 뭔가가 주어졌는데

// when : 이걸 실행했을 때

// then : 이런 결과가 나와야 해

1. service 패키지에 있는 MemberService 클래스 메소드들 테스트 코드 작성

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Optional;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach // 같은 memberRepository 객체를 사용하도록 수정된 코드
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
        // MemberService 클래스 입장에서 외부에서 new 하여 생성된 객체를 내부의 멤버변수에 넣어주고 있는데
        // 이런것을 dependency injection(DI) 이라고 함.
    }

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

    // 아래와 같이 해줬던 이유는 공유변수 초기화를 위해 clearStore() 메소드를 사용해야 하는데
    // MemberService의 멤버변수인 memberRepository가 private 인데 이를 접근할 수 있는 멤버함수가 없어서
    // MemoryMemberRepository의 내부에 선언된 HashMap은 static이라서 모든 객체가 공유하기에 아래와 같이 해줄 수 있었음.
    // 그런데 이 과정에서 엄연히 서로 다른 객체를 사용하고 있다는 것은 바람직 하지 않아 MemberService의 생성자를 수정하며 위와 같이 수정함.
//    MemberService memberService = new MemberService();
//    MemoryMemberRepository memberRepository = new MemoryMemberRepository();

    @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
    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 catch 문으로 아래와 같이 해볼 수 있겠으나 더욱 편리한 위의 문법이 제공됨
//        try{
//            memberService.join(member2); // 예외가 발생해야 하는 곳
//            fail(); // 예외가 발생하지 않고 여기까지 왔다면 문제가 있는것으로 처리.
//        } catch(IllegalStateException e){
//            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        }

        //then
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

2. 테스트 말고 일반 service 패키지에 있는 MemberService 클래스 코드 일부 수정

// 기존코드
private  final MemberRepository memberRepository = new MemoryMemberRepository();

// 외부에서 MemoryMemberRepository 객체를 받도록 수정된 코드
private  final MemberRepository memberRepository;
MemberService(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
}
package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;

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

public class MemberService {

    // 기존코드
    //private  final MemberRepository memberRepository = new MemoryMemberRepository();

    // 외부에서 MemoryMemberRepository 객체를 받도록 수정된 코드
    private  final MemberRepository memberRepository;
    MemberService(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }

    /*
    * 회원가입
    * */
    public Long join(Member member){
        //같은 이름이 있는 중복 회원이 있다면 예외를 발생시킴

        // 직관적인 코드
//        Optional<Member> result = memberRepository.findByName(member.getName());
//        result.ifPresent(m -> { // ifPresent : 만약 값이 있다면 즉, null이 아니라면 실행부를 실행하라.
//            throw new IllegalStateException("이미 존재하는 회원입니다.");
//        });

        //위 코드를 간소화 시킨것
//        memberRepository.findByName(member.getName())
//                .ifPresent(m -> {
//                    throw new IllegalStateException("이미 존재하는 회원입니다.");
//                });

        //바로 위 코드를 드래그 해서 Alt + Enter를 눌러 extract method 하여 생성한 메소드
        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();
    }

    /*
     * 1개 회원 조회
     * */
    public Optional<Member> findOne(Long memberId){
        return memberRepository.findById(memberId);
    }
}
Comments