ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring boot #3 간단한 회원 서비스 및 테스트 구현
    Study/Spring boot 2021. 7. 8. 17:58

    1. 어플리케이션 구조

    아래 요구사항을 만족하는 간단한 회원들을 관리하는 어플리케이션을 구현한다고 가정하자.

    데이터 : id, 이름

    기능 : 회원 등록, 조회

     

    controller, service, repository, domain으로 총 4가지로 나누어서 각각 구현할 것이다.

     

    service는 클라이언트와 밀접한 로직을 수행하는 역할이고 domain은 사용될 객체를 의미한다.

    repository는 DB와 같이 작동하고, 데이터를 관리하는 역할이다. 일단은 DB는 없이 진행할 것이므로  인터페이스로 관리하자.

     

     

    2. 도메인과 리포지토리 구현

    앞에서 만든 프로젝트에서 계속 이어서 코드를 구현해보자

     

    먼저 회원 객체가 필요하므로 도메인을 구현해야한다. java/[프로젝트이름/ 디렉토리내에 domain 패키지를 생성하고 Member 클래스를 생성하자. 그리고 아래와 같이 id, name 맴버변수를 생성해주자.

    public class Member {
        private Long id;
        private String name;
        
    }

    그럼 이제 private으로 지정하였으니, 이 변수에 접근을 해줄 메소드가 필요하다. 이때 window같은 경우 맴버변수들을 드래그한 후 Alt + insert를 누른후 getter and setter을 누르면 아래와 같이 자동으로 메소드를 생성해준다! 매우 편리한 기능이다.

    public class Member {
    
        private Long id;
        private String name;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

     

     

    그럼 도메인도 생성했으므로 저장소의 역할을 할 repository를 구현해보자. 먼저 repository라는 패키지를 생성 후, MemberRepository라는 인터페이스를 만들자. 나중에 무슨 DB를 사용할지 모르므로 인터페이스로 쉽게 관리하기 위함이다. 그리고 필요한 메소드들을 아래와 같이 추상메소드로 선언해주자.

    public interface MemberRepository {
        Member save(Member member);
        Optional<Member> findById(Long id);
        Optional<Member> findByName(String name);
        List<Member> findAll();
    }

     

    그리고 같은 디렉토리에 구현클래스인 MemoryMemberReposiory 클래스를 생성하자! 그리고 implements로 인터페이스를 상속해주는데, 아래 사진처럼 alt + Enter를 눌려 아래 implement methods를 선택하면 한번에 추상메소드를 상속할 수 있다! 

     

     

    그리고 아래처럼 각 메소드들을 구현해주자! 모든 객체가 store과 squence를 공유하기 위해 static으로 선언해주자! 이때 Optional<> 사용하는 이유는 리턴값이 Null 값일수도 있기 때문이다! Null을 감싸기 위해 사용하는것이다.

    public class MemoryMemberRepository implements MemberRepository{
    
        private static Map<Long, Member> store = new HashMap<>();
        private static long squence = 0L;
    
        @Override
        public Member save(Member member) {
            member.setId(++squence);
            store.put(member.getId(), member);
            return member;
        }
    
        @Override
        public Optional<Member> findById(Long id) {
            return Optional.ofNullable(store.get(id));
        }
    
        @Override
        public Optional<Member> findByName(String name) {
            return store.values().stream()
                    .filter(member -> member.getName().equals(name))
                    .findAny();
        }
    
        @Override
        public List<Member> findAll() {
            return new ArrayList<>(store.values());
        }
    
        public void clearStore() {
            store.clear();
        }
    }

     

     

     

    3. 도메인과 리포지토리 테스트 작성

    리포지토리를 간단하게 구현해 보았다. 근데 우리는 아직 이 코드가 제대로 동작하는지 알 수 없다. 어찌어찌 잘 작동되는지 확인해도 만약 중간에 코드가 변경되면 다시 검사를 해야할 수도있다. 그래서 우리는 이럴때를 위해서 테스트 코드를 작성한다. 테스트코드는 작성한 기능이 제대로 동작하는지 확인함이다.

    test 하위 디렉토리에 repository 패키지를 만들고 직접 test 파일을 생성하거나 class를 택하고 ctrl + shift + t 를 누르면 바로 테스트파일 생성해주도 뼈대도 알아서 뼈대를 작성해준다!

     

    MemoryMemberRepository repository = new MemoryMemberRepository();

    그리고 테스트에 사용될 저장소 하나 만들어주자!

     

    먼저 save메소드가 잘 작동되는지 확인해보자! 아래처럼 오태식 하나를 만들어주다. 그리고 repository의 save 메소드로 오태식이를 저장해주자. 만약 제대로 동작했으면 findById로 찾은 맴버가 오태식이 일 것이다. 같음을 증명하기 위해서는 assertThat을 사용해주자 사용하기 위해서 아래처럼 import 해주자

    import static org.assertj.core.api.Assertions.assertThat;

    @Test
        public void save(){
            Member member = new Member();
            member.setName("오태식");
    
            repository.save(member);
    
            Member result = repository.findById(member.getId()).get();
            assertThat(member).isEqualTo(result);
    
        }

     

     

    이번에는 findByName이 제대로 동작하는지 테스트해보자. 오태식이와 육태식이를 만들고 레포에 저장하자. 그리고 만약 오태식이를 findByName으로 찾으면 오태식이가 반환될 것이다. 그래서 아래처럼 반환 객체가 오태식이가 맞는지, 그리고 육태식이는 아닌지 검증해주자

    @Test
        public void findByName(){
            Member member1 = new Member();
            member1.setName("오태식");
            repository.save(member1);
    
            Member member2 = new Member();
            member2.setName("육태식");
            repository.save(member2);
    
            Member result1 = repository.findByName("오태식").get();
    
            assertThat(result1).isEqualTo(member1);
            assertThat(result1).isNotEqualTo(member2);
    
        }

     

     

    그리고 위 두 테스트 메소드들이 같은 repository를 공유한다는걸 알 수 있다. 이런 점때문에 테스트가 원치 않게 진행될 수 있으므로 아래처럼 @AfterEach 어노테이션으로 매 테스트마다 레포를 비워주자.

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

     

     

    그리고 테스트 클래스를 실행해보면 아래처럼 초록불을 확인할 수 있다! 만약 코드를 수정했는데 빨간불이 뜨면 코드를 다시 한번 봐야한다.

     

     

     

    4. 서비스 구현

     이번엔 비지니스 로직인 서비스클래스를 구현하자. 일단 간단하게 회원가입을 구현해보자

    service 패키지를 생성하고 MemberService클래스를 생성하자. 그리고 Join 매소드를 생성하자. save로 인자로 받은 member를 저장하면 되는데, 이름이 중복되면 안된다. 그리므로 아래처럼 validateDuplicate 메소드가 필요하다. 그리고 만약 중복이 존재할시 에러를 던저주자.

        public Long join(Member member){
            validateDuplicate(member);
            memberRepository.save(member);
            return member.getId();
        }
    
        private void validateDuplicate(Member member) {
            memberRepository.findByName(member.getName())
                    .ifPresent(m->{
                        throw new IllegalStateException("이미 이름이 존재함");
                    });
        }

     

     

    5. 서비스 테스트 작성

    위에서 작성한 join 메소드의 테스트를 작성해보자. 이번에는 근데 회원가입이 잘되는지 테스트도 해봐야하지만, 중복일때 제대로 에러를 던지는지도 검사해야한다. 일단 회원가입을 검증은 쉽다. 아래철머 join으로 오태식이를 넣어주고 태식이가 잘 들어갔는지만 검사하면된다.

    다음에는 member1, member2를 각각 만들어주고 오태식로 동명이인인 맴버 2개를 만들어주자. 그리고 레포에 집어 넣어주는데 member2 넣어줄 때 에러가 발생해야한다. 그러므로 assertThrows를 이용해서 검사해주자. 인자로는 첫번째는 나와야하는 에러의 클래스고, 2번쨰 인자는 실행할 메소드가 들어간다. 

        @Test
        void join() {
            //given
            Member member = new Member();
            member.setName("오태식");
    
            //when
            Long id = memberService.join(member);
    
            //then
            Member find = memberService.findOne(id).get();
            assertThat(member.getName()).isEqualTo(find.getName());
        }
    
        @Test
        public void validateDuplicate(){
            //given
            Member member1 = new Member();
            member1.setName("오태식");
    
            Member member2 = new Member();
            member2.setName("오태식");
    
            //when
            memberService.join(member1);
            assertThrows(IllegalStateException.class, () -> memberService.join(member2));
    
        }

     

     

    6. 의존성 주입 DI

    아래처럼 MemberService에서 reposiory를 new 생성자를 통해 생성해 사용할 경우 MemberService는 MemoryMemberRepository 클래스에 의존성이 생기게 된다. 그래서 이러한 의존성을 제거하기 위해 외부로부터 클래스를 주입받을 필요가 있다.

     

     

    그래서 아래처럼 생성자에서 레포 객체를 주입받게 하면 의존성이 없어진다. 이렇게 함으로서 MemberRepository 인터페이스를 상속하는 다른 클래스를 주고 싶을때 코드 수정없이 생성자로 전달하면 된다.

     
     
     
     

    댓글

Designed by Tistory.