회원 관리 예제

구름코딩·2020년 10월 16일
0

개요도

  • 비지니스 요구사항 정리
  • 회원 도메인과 리포지토리 만들기
  • 회원 리포지토리 테스트 케이스 작성
  • 회원 서비스 개발
  • 회원 서비스 테스트

비지니스 요구사항 정리

  • 데이터 : 회원ID, 이름
  • 기능 : 회원등록, 조회
  • 아직 데이터 저장소가 선정되지 않은 설정(mysql, nosql 등)

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

  • 컨트롤러 : 웹 MVC의 컨트롤러 역활 또는 API를 만들때의 컨트롤러 역활
  • 서비스 : 서비스 클래스의 핵심 비지니스 로직이 들어가있다 ex) 중복가입 방지, 비번 설정 등
  • 도메인 : 회원, 주문, 쿠폰 등 데이터베이스에 주로 저장되고 관리되는 비지니스 도메인 객체이다
  • 리포지토리 : 비지니스 도메인 객체를 이용해서 핵심 비지니스 로직이 동작하도록 구현한 계층

클래스 의존관계

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

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

구조

Member 클래스

데이터 : 회원의 시스템상 ID, 회원의 이름

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 인터페이스

기능 : 회원 등록, 조회(id, 이름, 전체)

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

MemoryMemberRepository 클래스 (인테페이스 구현)

외부 저장소 대신 사용할 임시 내부 메모리 저장소이다

public class MemoryMemberRepository implements MemberRepository{

    //데이터가 저장되는 곳
    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L; // 0, 1, 2 .. 등 key값을 생성해주는 역활

    @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(m -> m.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }
    
    // 각 테스트가 끝날을 때마다 Map을 지워주는 메소드
    public void clearStore(){
        store.clear();
    }
}

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

일반적인 테스트 방법

  • 자바의 main메서드를 통해서 실행
  • 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행
    • 단점 : 준비 및 실행에 시간이 오래 걸리고, 반복 실행하기 어려우며, 여러 테스트를 한번에 실행하기 어렵다

JUnit 프레임워크를 이용한 테스트

class MemoryMemberRepositoryTest {

    MemberRepository repository = new MemoryMemberRepository();

    @Test
    public void save(){
        //이름을 "Spring"으로한 Member객체를 만들고 저장한 후
        Member member = new Member();
        member.setName("Spring");

        repository.save(member);

        // 저장된 객체의 id를 이용해서 찾아낸 객체와 같은지 비교
        Member result = repository.findById(member.getId()).get();
        
        // 출력을 통해 확인하는 경우
        System.out.println("result = " + (member == result));
        
        본 값이 기대한 값과 같은지 다른지 체크해주는 함수를 이용할수도 있다
        
        1. org.junit.jupiter.api 를 이용한 Assertions
        
        Assertions.assertEquals(member, result); <-- 같은 경우
        Assertions.assertEquals(member, null); <-- 다른 경우
        
        2. org.assertj.core.api.Assertions 를 이용한 Assertions
        문법이 좀더 직관적이다
        
        Assertions.assertThat(member).isEqualTo(result);
        
        Assertionsstatic 변수로 변환해서 더 편하게 사용할수도 있다
        //import static org.assertj.core.api.Assertions.*;
        assertThat(member).isEqualTo(result);
    }
    
    @Test
    public void findByName(){
        Member member1 = new Member();
        member1.setName("woonsik");
        repository.save(member1);

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

        Member result = repository.findByName("woonsik").get();
        assertThat(member1).isEqualTo(result);
    }

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

        Member member1 = new Member();
        member1.setName("woonsik1");
        repository.save(member1);

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

        List<Member> result = repository.findAll();
        assertThat(result.size()).isEqualTo(3);
    }
}

같은 경우
다른 경우

테스트에서 각 테스트의 순서는 보장되지 않는다

  • 따라서 순서에 기반한 Test케이스를 짜면안된다
  • 각 테스트가 끝날때 마다 데이터를 초기화해줘야 한다

@afterEach()를 이용하여 구현 - 각 @Test가 끝났을때마다 해당 메소드를 실행해준다

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

Test 주도 개발 TDD

  • Test-Deriven Development

위에서처럼 기능을 먼저 구현한 후에 테스트케이스를 완성하는 것이 아니라 내가 만들려는 기능의 테스트케이스를 먼저 구현한 후에 기능을 구현하는 방식이다

예를들어 삼각형 별찍기 프로그램이라면 별을 찍는 함수를 먼저 짜는 것이 아닌 삼각형의 별을 체크하는 테스트를 먼저 만들고 삼각형 별찍는 프로그램을 만드는 순서이다

profile
내꿈은 숲속의잠자는공주

0개의 댓글