본문 바로가기

스프링

회원 관리 예제

728x90

비즈니스 요구사항 정리

 

 

- 회원은 중복가입이 안된다 등의 로직이 서비스에 들어가 있음

- 비즈니스 도메인 객체를 가지고 핵심 비즈니스 로직이 구현되도록 한 것이 서비스

 

 

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

* 인터페이스 상속받은 다음에 그 위에서 alt+엔터하면 메소드 가져올 수 있음

 

 

도메인 패키지

회원 객체

package hello.hellospring.domain;

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

 

리포지토리 패키지

회원 리포지토리 인터페이스

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

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

public interface MemberRepository {
    //회원이 저장소에 저장
    Member save(Member member);
    //값을 받아올 때 null값인 경우 이를 반환할 때 옵셔널로 감싸서 많이 반환
    //findby~를 하면 저장소에 있는 정보를 찾아올 수 있음
    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{

    //save를 할 곳
    private static Map<Long,Member> store = new HashMap<>();
    private static long sequence =0L;

    @Override
    public Member save(Member member) {
        //member에 넣기 전에 id 값 세팅
        member.setId(++sequence);
        //스토어에 id저장
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        //null일 가능성이 있으면 아래처럼 감싸서 반환
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        //member의 name이 파라미터에서 넘어온 name과 같은지 확인
        //같은 경우이면 핉터링, 그냥 찾으면 반환
        //람다를 사용해 루프를 돌면서 필터에 걸리면 바로 반환을 하는거고 
        //걸리지 않으면 옵셔널을 사용해서 null을 반환하는 것
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        //store에 있는 member들을 쭉 반환
        return new ArrayList<>(store.values());
    }
    
    public void clearStore(){
        //store를 싹 비우는 메서드
        store.clear();
    }
}

 

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

 개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다. 

 

 이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다. 

 

 자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.

 

package hello.hellospring.repository;

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

import java.util.List;

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

public class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

    //메서드가 끝날 때마다 어떠한 동작을 하도록 하는 어노테이션
    @AfterEach
    public void afterEach(){
        //하나의 테스트가 실행이되고 끝날 때마다 저장소를 다 지움
        //순서와 상관없이 메서드를 개별적으로 테스트 가능
        repository.clearStore();
    }

    @Test
    //@Test를 사용하면 아래의 메서드를 그냥 실행할 수 있음
    public void save(){
        Member member = new Member();
        member.setName("spring");
        
        repository.save(member);
                                                    //옵셔널에서 꺼내옴
        Member result = repository.findById(member.getId()).get();
        //검증 - new에서 저장한거랑 DB에서 꺼낸거랑 같으면 참
        //member가 result와 같아야 함
        assertThat(member).isEqualTo(result);
        //sava() 실행해서 같으면 초록체크, 아니면 노란x
    }

    @Test
    public void findByName(){

        //spring1, spring2라는 두명의 회원이 가입이 된 것
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

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

        //현재 result는 spring1을 가지고 있기 때문에 member1과 비교하면 true
        assertThat(result).isEqualTo(member1);
    }

    @Test
    public void finAll(){
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();
        
        //아이디 2개 저장해서 회원이 2이기 때문에 2가 되어야 함
        assertThat(result.size()).isEqualTo(2);
    }
}

 

 

테스트는 각각 독립적으로 실행되어야 하기 때문에 순서에 의존하며 테스트하면 안된다.

따라서 위의 내용처럼 AfterEach를 사용해 하나의 테스트가 끝날 때마다 저장소를 비워줘야 한다.

 

회원 서비스 개발

회원 서비스 클래스 - 회원 리포지토리와 도메인을 사용해서 실제 비즈니스 로직을 작성하는 쪽

 

서비스 클래스는 비즈니스에 가까운 용어를 사용해서 메서드를 만들어야 함

 

 

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 {

    //원래 클래스와 테스트 클래스에서 만들어진 new MemoryMemberRepository()는 서로 다른 객체이기 때문에 조금 애매한 감이 있음
    //즉 다른 리포지토리를 이용하고 있는 것
    //같은 객체를 사용하는 것이 좋고 굳이 다른 객체로 2개를 쓸 이유가 없음

   // private  final MemberRepository memberRepository = new MemoryMemberRepository();

    //테스트 클래스와 같은 인스턴스를 사용하게 바꾸려면 아래와 같이 바꾸면 됨
    private  final MemberRepository memberRepository;
    //직접 new로 생성하는 것이 아니라 생성자를 사용해서 외부에서 넣어주게 바꿔주면 됨
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    //회원 가입
    public Long join(Member member){
        //중복 이름 회원 x라고 조건
        validateDuplicationMember(member); //중복회원 검증
        
        
        memberRepository.save(member);
        return  member.getId();
    }
    
    //아래와 같은 긴 내용이 있는 경우에는 내용을 작성하고 ctrl+alt+m을 하면 메서드를 하나 만들어버림
    private void validateDuplicationMember(Member member) {
        Optional<Member> result = memberRepository.findByName(member.getName());
        //member에 값이 있으면 동작 실행함
        //옵셔널이기 때문에 가능한 것
        //회원가입을 했을 때 중복 회원이 있으면 오류가 발생하는지 검증을 해봐야함 - 테스트 클래스 작성
        result.ifPresent(m -> {
             throw new IllegalStateException("이미 존재하는 회원입니다.");
         });
    }
    //전체 회원 조회
    public List<Member> findMembers(){
        //findAll의 리턴 타입이 list<Member>이기 때문에 그냥 return해주면 끝남
        return memberRepository.findAll();
    }
    
    public Optional<Member> findOne(Long memberId){
        return memberRepository.findById(memberId);
    }
}

 

회원 서비스 테스트

테스트하려는 클래스 위에서 ctrl+shift+t를 하면 테스트 클래스 바로 만들 수 있음(껍데기)

 

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 static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService;

    //원래 클래스와 테스트 클래스에서 만들어진 new MemoryMemberRepository()는 서로 다른 객체이기 때문에 조금 애매한 감이 있음
    //즉 다른 리포지토리를 이용하고 있는 것
    //같은 객체를 사용하는 것이 좋고 굳이 다른 객체로 2개를 쓸 이유가 없음
    MemoryMemberRepository memberRepository;

    @BeforeEach //각 테스트가 실행하기 이전에 먼저 실행
    public void beforeEach(){
        //MemoryMemberRepository를 먼저 만든다
        memberRepository = new MemoryMemberRepository();
        //위의 객체를 memberServie의 매개변수에 넣어줌 - 같은 MemoryMemberRepository사용하게 됨
        //이를 DI라고 함
        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();
        //저장한 이름이 리포지토리에 있는 이름과 같은지
        Assertions.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
        //2개의 이름이 모두 같기 때문에 중복검사인 validate메서드에서 예외가 터짐
        memberService.join(member1);
        //memberService.join(member2)로직을 실행할건데 IllegalStateException.class예외가 터져야 함
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

        Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        //정상적으로 터지면 초록 체크
        
        //이렇게 사용해도 되긴 함
//        try {
//            memberService.join(member2);
//            //예외처리로 안넘어가고 밑으로 내려오면 실패
//            fail();
//        }catch (IllegalStateException e){
//            //여기로 오면 정상 실행된 것
//            Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//            //초록 체크로 뜨면 제대로 메시지가 출력된 것
//        }
        
        //then
    }
    
    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

 

 

@BeforeEach : 각 테스트 실행 전에 호출된다. 테스트가 서로 영향이 없도록 항상 새로운 객체를 생성하고, 
의존관계도 새로 맺어준다.

728x90

'스프링' 카테고리의 다른 글

스프링 DB 접근 기술  (0) 2022.05.20
회원 관리 예제 - 웹 MVC 개발  (0) 2022.05.19
스프링과 빈의 의존관계  (0) 2022.05.19
스프링 웹 개발 기초  (0) 2022.05.18
프로젝트 환경 설정  (0) 2022.05.17