백엔드 개발자 시작 3일차

백엔드 개발자·2023년 3월 22일
0

Back-End

목록 보기
2/2

회원관리 예제

1. 비즈니스 요구사항

  • 데이터 : 회원 ID, 이름
  • 기능 : 회원 등록, 조회
  • 데이터 저장소는 정해지지 않아 가상의 시나리오로 진행

  • 메모리 기반의 데이터 저장소는 간단하게 만들 수 있다. (DB가 정해지지 않았기 때문에 가상의 저장소 구현)

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

- domain class

package hello.hellospring.domain;

public class Member {

    private Long id;        // 시스템이 저장하는 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;
    }
}

- MemberRepository interface

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

- MemoryMemberRepository class

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 seq = 0L;

    @Override
    public Member save(Member member) {

        member.setId(++seq);
        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());
    }
}
  • stream에 대해 잘 모르기 때문에 간단하게 찾아서 정리했다.
    - store.values().stream() => stream으로 만든다.
    - stream().fiter(member -> member.getName().equals(name)) :
    stream을 사용해서 filtering을 하는데 member 의 Name과 현재 찾고자 하는 name을 찾아 stream으로 return
    - .findAny() : findAny는 filtering된 stream에서 첫 번째 요소를 가져온다. 병렬로 처리하여 사용하게 되면 첫 번째가 아닌 다른 요소들도 가져올 가능 성이 있다.

3. 테스트 케이스 작성

JUnit 이라는 프레임워크로 테스트를 실행한다.

** main 메서드를 통해서 실행하거나 웹 애플리케이션의 컨트롤러를 통해 기능을 실행하는경우 준비하고 실행하는데 오래걸리고, 반복 실행하기 어려운 문제가 있음

보통 동일한 이름의 package를 생성하고 class에는 뒤에 Test를 붙여서 구분한다.

MemoryMemberRepositoryTest class (Test 코드)

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;

class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach                      // 모든 메소드가 끝나고 실행되는 동작을 지정 (콜백메소드)
    public void afterEach(){
        repository.clearStore();
    }

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();

        //Assertions.assertEquals(member, result);
        Assertions.assertThat(member).isEqualTo(result);    
    }

    @Test
    public void findByName(){
        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();

        Assertions.assertThat(member1).isEqualTo(result);
    }

    @Test
    public void findAll(){
        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();

        Assertions.assertThat(result.size()).isEqualTo(2);
    }
}
  • @Test : 이 코드를 Test하겠다는 Annotation
  • @AfterEach : 이 class안에 있는 메소드를 실행이 끝나면 동작되는 메소드를 지정하는 Annotation
    Test코드는 순서에 종속되어 실행되는 것이 아니고 랜덤으로 돌아가는 특성을 가지고 있다.
    findAll과 findByName을 놓고 보자.
    findAll이 먼저 실행되고 findByName이 실행되게 된다면 동일한 이름(spring1/spring2)으로 이미 findAll에서 저장이 되어 있기 때문에 findByName에서 Error가 발생한다.
    이를 해결하기 위해서 clear시켜주는 메소드를 작성한다.
    clearStore메소드는 MemoryMemberRepository 클래스에 선언해준다.
public void clearStore(){
        store.clear();
    }
  • Assertions : Assertions는 총 두가지가 있는데 assertj와 JUnit이 있다.
    assertThat(obejct1).isEqualTo(object2)는 1과 2가 동일한지 확인하는 메소드
    동일하지 않으면 Test실행 시 Error가 발생하게 된다. 보통 System.Out.println이런걸로 확인하지 않는다.

  • 테스트코드가 여러개인 경우 해당 package를 모두 Run해보면 된다.

4. 회원서비스 로직 개발

- Member Service Class

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();
    private final MemberRepository memberRepository;

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

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

//        Optional<Member> result = memberRepository.findByName(member.getName());
//        result.ifPresent(m -> {
//            throw new IllegalStateException("이미 존재하는 회원입니다.");
//        });

        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);
    }
}
  • 테스트 할 때MemberRepository가 중복으로 사용되지 않도록 분리시켜 생성되도록 한다.
  • Optional의 기능을 활용하여 중복을 검사 한다.
    result.ifPresent(m -> { throw new Exception() }) : result(Optional 객체)가 존재할 경우 반환 할 Exception을 지정할 수 있다.
    주석 처리된 방법을 사용해도 좋고 validateDuplicateMember와 같이 메소드를 만들어도 된다.

- MemberServiceTest

  • Ctrl + Shift + T 를 누르게 되면 간편하게 Test Code를 작성할 수 있다.
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.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    /*
    MemberService memberService = new MemberService();
    MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository();   // 다른 인스턴스 이기 때문에 문제가 생길 수 있음. 현재는 Map을 static으로 선언해놓기 때문에 문제가 없겠지만 static이 아니게 되면 문제가 발생
    */

    MemberService memberService;
    MemoryMemberRepository memoryMemberRepository;

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

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

    @Test
    void join() {       // 메소드명을 한글로 바꿔도 괜찮음
        //given
        Member member = new Member();
        member.setName("hello");

        //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
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));// 반환 값을 받아서 메시지도 검증
        Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

        // assertThrows(IllegalStateException.class, () -> memberService.join(member2));// IllegalStateException 예외 처리가 정상적으로 되므로 에러가 없이 진행됨.
        // assertThrows(NullPointerException.class, () -> memberService.join(member2));   // NullPointerException 경우 예외 처리가 정상적으로 되지 않으므로 에러 발생

        /*  아래와 같은 방법으로 exception check가 가능하다. try 안의 내용을 실행하다가 exception이 발생되면 성공한 것이므로 에러가 없다.
        try{
            memberService.join(member2);
            fail();
        } catch (IllegalStateException e) {

        }
        */

        //then


    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}
  • given, when, then 문법을 사용하여 순차적으로 구현
  • 중복 예외 처리를 확인하는 방법은 여러가지가 있으므로 코드를 확인한다.
  • findMembers와 findOne은 미완성으로 강의가 끝나게 된다. 내가 직접 해보자.

0개의 댓글