공부/Spring - 김영한

[ spring ] 예제 - 회원 관리 : 도메인, 리포지토리, 서비스, 테스트케이스

haena02 2023. 1. 12. 21:47
반응형

1. 비지니스 요구사항 정리

  • 데이터: 회원ID, 이름
  • 기능: 회원 등록, 조회
  • 아직 데이터 저장소(DB)가 선정되지 않음(가상의 시나리오)

일반적인 웹어플리케이션 계층구조

컨트롤러: 웹 MVC의 컨트롤러 역할

서비스: 서비스의 핵심 비지니스 로직이 들어있음 (ex.로그인 횟수제한)

리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리 

도메인: 비지니스 도메인 객체 (ex. 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨)

 

 

클래스 의존관계

 

데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계

(데이터 저장소는 RDB, NoSQL 등 고민중인 상황으로 가정)

초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용

 

2. 회원 도메인과 리포지토리 만들기

 

//멤버클래스

package hello.hellospring.domain;

public class member {

    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

 

//멤버Repository 인터페이스

package hello.hellospring.repository;


import hello.hellospring.domain.member;

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

public interface MemberRepository {

    member save(member member);
    Optional<member> findById(Long id);
    Optional<member> findByName(String name);
    List<member> findAll();

}

 

package hello.hellospring.repository;

import hello.hellospring.domain.member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository{

    private static Map<Long, member> store= new HashMap<>();
    private static long sequence = 0L;  //키값을 생성해주는 친구


    @Override
    public member save(member member) {
        //이름은 입력받고 아이디는 시스템에서 지정
        member.setId(++sequence);
        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) {     //null이 나와도 처리가능!
        return store.values().stream()
                .filter(member->member.getName().equals(name)) //쭉 돌면서 같은 이름이 있나 찾기
                .findAny(); //하나라도 찾으면
    }

    @Override
    public List<member> findAll() { //실제 실수에서 리스트 많이 씀
        return new ArrayList<>(store.values());
    }
}

 

3. 회원 리포지토리 테스트 케이스 작성

 

test라는 폴더 안에 데스트 하기 위한 class를 만들어서 테스트를 진행한다.

package hello.hellospring.repository;

import hello.hellospring.domain.member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Member;

class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach //한 테스트 클래스 끝날때마다 실행됨
    public void afterEach(){
        repository.clearSore();
    }

    @Test
    public void save(){
        member member = new member();
        member.setName("spring");

        repository.save(member);

        member result = repository.findById(member.getId()).get();
        //optional에서 값을 꺼낼때는 get으로 꺼낼 수 있음.

        //메모리에서 꺼낸것과 저장한 값이 똑같다면 참
        Assertions.assertEquals(result,member);
        org.assertj.core.api.Assertions.assertThat(member).isEqualTo(result)
        //정산실행되면 참 빨간불뜨면서 에러 뜨면 거짓

    }

    @Test

    public void findByName(){
        member member1 = new member();
        member1.setName("spring1");
        repository.save(member1);

        //shift+F6으로 한번에 이름바꾸기 가능
        member member2 = new member();
        member2.setName("spring2");
        repository.save(member2);

        member result = repository.findByName("spring1").get();

        org.assertj.core.api.Assertions.assertThat(result).isEqualTo(member1);

    }

    @Test
    public void findAll(){

        member member1 = new member();
        member1.setName("spring1");
        repository.save(member1);

        //shift+F6으로 한번에 이름바꾸기 가능
        member member2 = new member();
        member2.setName("spring2");
        repository.save(member2);

        List<member> result= repository.findAll();

        org.assertj.core.api.Assertions.assertThat(result.size()).isEqualTo(2);
    }
}

 

Assertions에는 편리한 기능들이 들어있다. 

이를 이용하면 출력값을 하나하나 비교해 볼 필요가 없고 정상실행되는지 안되는지만 확인하면된다. 

Assertions.assertEquals(result,member);
org.assertj.core.api.Assertions.assertThat(member).isEqualTo(result)

위 두 코드는 두개의 값이 값은지 비교해주는 두가지 방법이다. 

 

findAll과같은 함수는 size가 맞는지 비교해봄으로써 테스트를 할 수 있다. 

 

 

4. 회원 서비스 개발

 

회원 서비스는 회원 repository와 도메인을 이용하여 실제 로직을 짜는것이다. 

 

회원 중복확인을 하는 과정에서 findByName함수를 사용하게되는데, 이 함수의 리턴타입은 Optional로 감싸져있다.

그렇기 때문에 이 isPresent와 같은 편리한 함수를 사용할수있다.

 

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.IllformedLocaleException;
import java.util.Optional;

public class MemberService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();

    //회원가입
    public Long join(member member) {

        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    //중복회원검증
    private void validateDuplicateMember(member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> { //optional를 사용했기 때문에 사용가능
                    throw new IllegalStateException("이미 존재하는 회원입니다. ");
                });
    }

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

    //회원검색
    public Optional<member> findOne(Long memberId){
        return memberRepository.findById(memberId);
    }
}

서비스 파드는 그전 파드보다 훨씬 실무적인 느낌이 난다. 

이렇게 하는것이 좋다

 

5. 회원 서비스 테스트 

 

테스트하고싶은 클래스를 켜둔 상태에서 Ctrl+Shift+T 를 입력하면 손쉽게 테스트케이스를 만들 수 있다. 

테스트는 함수이름을 한글로 해도된다. (영어권사람들이랑 같이쓰는게 아니라면)

빌드할 때 테스트는 포함되지도 않기 때문데 보기좋게 한글로 해도된다. 

 

given - when - then 형식으로 으로 짜는것을 추천!

 

테스트는 정상작동을 확인하는 것도 중요하지만

예외처리를 잘 하고 있는가를 확인하는것이 더 중요!

 

*shift + f10 : 전에 쓰던 페이지로 이동

 

테스트 코드를 만들다보면 객체안에서 만들 객체와 내가 사용하는 객체가 같은 클래스이지만

객체는 달라서 원하는대로 움직이지 않을 수 있다.

그렇기 때문에 객체 안에서 객체를 만들때 새로 new 해주지 않고 외부에서 만들어진 것을 활용하도록 할 수 있다.

이 방법을 DI(Dependency Injection)라고한다. 

 

package hello.hellospring.service;

import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
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 hello.hellospring.domain.member;


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

class MemberServiceTest {

    MemberService memberService;
     MemoryMemberRepository memberRepository;

    @BeforeEach
    public void BeforeEach(){ //DI:직접 new 안하고 외부에서 넣어주는 방법 Di
        memberRepository = new MemoryMemberRepository();
        memberService=new MemberService(memberRepository);
    }

    @AfterEach //한 테스트 클래스 끝날때마다 실행됨
    public void afterEach(){
        memberRepository.clearSore();
    }

    @Test
    void 회원가입() { //한글로해도됨
        //회원가입을 한 회원과 리포지토리에 저장된 회원이 같은가?를 검증

        //given
        member member=new member();
        member.setName("hello");

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

        //then
        member findMember=memberService.findOne(saveId).get();
        org.assertj.core.api.Assertions.assertThat(member.getName()).isEqualTo(findMember.getName());
    }


    @Test
    public void 중복_회원_예외(){
        //테스트를 여러번하면 데이터가 계속 누적되기 때문에 두번쨰 시도에서는 첫번째 회원가입도 막힘!

        //given
        member member1=new member();
        member1.setName("spring");

        //then
        member member2=new member();
        member2.setName("spring");

        //when
        memberService.join(member1);
        //예외처리 좀 더 쉬운 문법, 오른쪽 인자를 실행했을 때 왼쪽 인자의 예외가 터져야한다.
        IllegalStateException e= assertThrows(IllegalStateException.class,()->memberService.join(member2)); //예외처리검증
        org.assertj.core.api.Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다. "); //메세지검증

        /*
        try{
            memberService.join(member2);
            fail("예외가 발생해야 합니다.");
        }catch(IllegalStateException e){
            org.assertj.core.api.Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다. ");
        }

         */
    }

    @Test
    void findMembers() {


    }

    @Test
    void findOne() {
    }
}

 

이 과정에서 MemberService의 리포지토리 선언부분이 아래와 같이 바뀌어야한다. 

    private final MemberRepository memberRepository;

    //외부에서 레포지토리를 넣어줄 수 잇도록
    public MemberService(MemberRepository memberRepository){
        this.memberRepository=memberRepository;
    }
반응형