[스프링] 회원 관리 예제 - 백엔드 개발

Kyu·2021년 2월 7일
0

Spring 공부기록

목록 보기
4/10

스프링 입문 강의 by 김영한

비즈니스 요구사항 정리

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

  • 컨트롤러: 웹 MVC의 컨트롤러 역할
  • 서비스: 도메인을 가지고 핵심 비즈니스 로직 구현 예를 들어, 회원은 중복가입 안된다 같은 기능.
  • 도메인: 회원,주문,쿠폰 등 db에 저장되는 비즈니스 도메인 객체
  • 리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리

  • 아직 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계
  • 데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 것으로 가정
  • 개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용

일단은 개발해야하니까 메모리에 넣었다 뺐다 할수있는 단순한 구현체를 만들 것이다. 나중에 데이터 저장소가 정해지면 바꿔끼워야 하기 때문에 인터페이스가 필요하다.

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


위와 같이 Member, MemberRepository, MemoryMemberRepository를 생성한다.

// Member.java

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;
    }
}
// MemberRepository.java

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.java

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) {
        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();
    }
}
  • 코드 이해하려면 -> 람다, Optional 배워야함

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

위에 작성한 코드가 정상적으로 동작하는지 검증하는 법이 테스트 케이스를 작성하는 것이다.

보통 테스트할 때 main 메서드를 실행하거나 웹 어플리케이션의 컨트롤러를 통해 해당 기능을 실행하는데, 이러한 방법은 준비하고 실행하는데 오래걸리고 반복 실행하기 어렵고 테스트를 한번에 실행하기 어렵다는 단점이 있다.

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

먼저 다음과 같은 경로에 Test 클래스를 만들어준다.

테스트 클래스를 만들 땐, 보통 테스트할 클래스 옆에 Test를 붙이고 똑같은 경로에 만들어주는게 관례다.

package hello.hellospring.repository;

import org.junit.jupiter.api.Test;

class MemoryMemberRepositoryTest {

    MemberRepository repository = new MemoryMemberRepository();

    @Test
    public void save() {

    }
}

다음과 같이 작성하고 save()를 실행해보면 무언가 실행이 된다.

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
        System.out.println("result = " +( result == member));
    }

이런식으로 콘솔에 테스트를 출려해볼 수도 있는데, assertions라는 기능이있다.
println()을 지우고 다음과 같은 코드를 넣어보자

Assertions.assertEquals(member, result);

아까와는 다르지만 테스트가 패스했다는 게 뜬다. 그런데 result대신에 뭐..아무거나 넣어본다면?

Assertions.assertEquals(member, null);
Expected :hello.hellospring.domain.Member@32cf48b7
Actual   :null

위와 같이 뜬다.

또 다른 방법은 똑같은 Assertions 인데 org.assertj.core.api에 있는 것이 있다.

Assertions.assertThat(member).isEqualTo(result);

실무에서는 빌드툴이랑 엮어서 빌드할떄 테스트케이스에서 통과하지못하면 다음단계로 못넘아고도록 막는다.

Junit 테스트케이스에 대해 배워보기

// 전체코드 
// MemoryMemberRepositoryTest.java

package hello.hellospring.repository;

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

import java.util.List;

class MemoryMemberRepositoryTest {

    MemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
        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(result).isEqualTo(member1);
    }

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

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

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

        Assertions.assertThat(result.size()).isEqualTo(2);
    }

}

근데 위에 코드를 전체 테스트케이스를 돌려보면 중간에 잘되던 findByName이 실패했다는 것을 볼 수 있다. 변수선언할때 순서가 잘못되서 그렇다 < 이해못함

그래서 각 메소드가 실행되고 난 뒤에 모드 클리어를 해줘야한다

MemoryMemberRepository에서 만들었던 clearStore()를 불러준다.

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

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 {

    private final MemberRepository memberRepository;

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

    /**
     * 회원가입
     */
    public long join(Member member) {
        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);
    }
}

회원 서비스 테스트

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;
    MemoryMemberRepository memberRepository;

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

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

    @Test
    void 회원가입() {
        //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("이미 존재하는 회원입니다.");

//        memberService.join(member1);
//        try {
//            memberService.join(member2);
//            fail();
//        } catch (IllegalStateException e) {
//            Assertions.assertThat(e.getMessage().isEqualTo("이미 존재하는 회원입니다."));
//        }

        //then
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}
profile
TIL 남기는 공간입니다

0개의 댓글