Java - Spring &&n SpringBoot

회원 관리 페이지 만들기 1) 도메인, 리포지토리(저장소), 테스트 실행

TerianP 2021. 12. 10.
728x90

1. 웹 어플리케이션 계층 구조 및 비지니스 요구사항

1) 웹 어플리케이션 계층 구조

  • 컨트롤러 : 웹 MVC 의 컨트롤러 역할
  • 서비스 : 비지니스 도메인 객체를 이용하여 핵심 비지니스 로직 구현
    • 중복 가입 불가
  • 리포지토리 : DB 에 접근 , 도메인 객체를 DB에 저장하고 관리
  • 도메인 : 비지니스 도메인 객체
    • 회원, 주문 , 쿠폰, 등등 주로 DB에 저장하고 관리되는 객체

 

2) 비지니스 요구사항 : 데이터, 기능, 데이터 저장소

  • 데이터 : 이름, 회원ID, 패스워드
  • 기능 : 회원 등록, 조회
  • 데이터 저장소(DB) : 강의에서는 따로 선정되지 않은 상황 → 추후 공부하면서 JPA, JDB사용 예정

 

3) 클래스 의존 관계

  • 개발은 DB가 선정되지 않았기 때문에 초기 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용
  • 추후 인터페이스로 구현 클래스를 변경할 수 있도록 설계
    • DB 는 RDB, NoSQ 등 다양한 저장소로 고민 중인 상황으로 가정 ⇒ 추후 JPA, JDB 등 변경 예정

 

 

2. 회원 도메인과 리포지토리(임시 DB) 생성

  • 회원 도메인 생성
package HJproject.Hellospring.domain;

public class Member {

    private Long code; // 시스템에서 저장 & 식별 구분 하기 위한 code
    private String name; // 고객 이름
    private String id; // 고객 id
    private String passwd; // 고객 passwd

    public Long getCode() {
        return code;
    }

    public void setCode(Long code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

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

    public String getId() {
        return id;
    }

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

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
}

 

  • 멤버 리포지토리 MemberRepository : 가입 저장을 위해 사용되는 인터페이스
package HJproject.Hellospring.repository;

import HJproject.Hellospring.domain.Member;

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

public interface MemberRepository {
    Member save(Member member);

    Optional<Member> findByCode(Long code);
    Optional<Member> findByName(String name);

    List<Member> findAll(); // 모든 회원 리스트 반환

}

 

  • 메모리 멤버 리포지토리 MemoryMemberRepository : 메모리에 저장하기 위해 사용되는 클래스
package HJproject.Hellospring.repository;

import HJproject.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.setCode(++sequence); // store 에 저장 전 member 값에 sequence 값을 하나 올려서 넣음
        store.put(member.getCode(), member); // store 에 member와 code와 함께 저장
        return member;
    }

    @Override
    public Optional<Member> findByCode(Long code) {
        return Optional.ofNullable(store.get(code));
        // optional.ofnullable 를 사용하면 null 이 반환되더라도 감쌀 수 있음
        // 추후 클라이언트에서 활용 가능

    }

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

 

 

3. 테스트 케이스 작성

  • 테스트 케이스를 따로 작성해서 사용하는 이유는 기존의 main 클래스를 사용해서 실행하거나 컨트롤러를 통해 실행하면 준비하는데 시간도 오래걸리고, 여러 테스트를 한번에 실행하기 어렵다는 단점이 존재한다.
  • 이에 따로 테스트 케이스 클래스를 만들어서 해당 파일을 실행하여 테스트하는 방식을 취하는게 효율적이다.

 

1) 테스트 케이스 코드 기본 : MomoryMemberRepositoryTest ⇒ 테스트 결과가 테스트 순서에 종속됨

  • 저장이 제대로 되는지 확인하는 테스트 코드
  • Name 을 기준으로 저장된 member 이 불러와지는지 테스트하는 코드 && 기준없이 저장된 내용 전체가 불러와지는지 테스트하는 코드
import HJproject.Hellospring.domain.Member;

import HJproject.Hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

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

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

class MomoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @Test // junit 관련
    public void save(){ // 저장이 잘 되는지 확인
        Member member = new Member();
//        Member member2 = new Member();

        member.setName("pray");
//        member2.setName("하늘");

        repository.save(member);
//        repository.save(member2);

//        System.out.println(member.getCode());
//        System.out.println(member.getName());

        Member result = repository.findByCode(member.getCode()).get();
        //  repository 안에 저장된 memeberCode 를 가져와서 result 에 저장

        // 기대값과 실제값을 확인하기 위해 Assertions 의 assertEquals 라는 메서드 활용

        // 검증 방법 1. Assertions.assertEquals(기대값, 실제값) => junit
//        Assertions.assertEquals(member, result);

        // 검증 방법 2. Assertions.assertThat(실제값).isEqualTo(기대값) => assertj
        assertThat(result).isEqualTo(member); // static import 상태

    }

    @Test // 이름 기준으로 불러오기
    public void findByName(){
        Member member1 = new Member();
        member1.setName("pray");
        repository.save(member1);

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

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

        assertThat(result).isEqualTo(member1);
    }

    @Test // 전체 내용 가져오기
    public void findAll(){
        Member memeber1 = new Member();

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

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

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

        assertThat(result.size()).isEqualTo(3);

    }
}

 

2) 테스트 케이스 코드 MomoryMemberRepositoryTest 2 : 테스트 케이스 실행 시 순서에 상관없이 결과를 확인하도록 하기위한 코드 ⇒ 테스트 결과가 테스트 순서에 독립적

  • 그런데 위에 있는 것처럼 코드를 짜게 되면 문제가 발생한다!!
    • 각각의 테스트를 개별로 테스트 하면 문제가 없지만 한꺼번에 전체 클래스 단위에서 테스트 하게 되면 하나의 테스트 뒤에 결과가 초기화 되지 않은 채 계속해서 진행되기 때문에 하나의 테스트 뒤에 메모리 데이터를 초기화 한 뒤 다시 테스트 하게 만들어야 한다.

수정하지 않으면 이렇게 오류가 발생한다

  • 이런 문제를 해결하기 위해 두 가지 파일에서 각각 아래에 해당하는 내용을 넣어준다.
  • MemoryMemberRepository
public void clearStore(){
        store.clear();
    }
  • MomoryMemberRepositoryTest
@AfterEach // 하나의 메서드가 끝나면 AfterEach 메서드 동작
public void AfterEach(){
    repository.clearStore();
}

오류 해결 완료!!

3) 테스트 케이스 작성에 관하여

  • 개발에 있어서 테스트 케이스를 작성하는 것은 굉장히 중요한 부분이다. 어느 부분에서 제대로 동작하는지 그렇지 않은지, 원하는 결과가 발생하는지 아닌지, 어떻게 고쳐야하는지를 확인할 수 있기 때문이다.
  • 테스트 케이스에 따른 개발 방법은 크게 2가지가 있다.
    • 첫 번째는 이번처럼 먼저 동작하는 코드(구현 클래스)를 개발하고 그 다음 테스트 케이스를 만들어서 검증하는 방법이다.
    • 두 번째는 테스트 주도 개발(TDD, Test Driven Development) 라는 것으로 테스트 케이스 틀을 먼저 만들고 그것에 맞춰 동작하는 코드(구현 클래스)를 개발해가는 방법이다
  • 테스트 케이스 전체를 실행해보려면 test 디렉토리안에 기본으로 만들어진 클래스 파일을 실행하면 된다.

 

4. 기존에 코드에 추가해보기 : 내 맘대로 수정하기!!

  • 기존의 내용에 내가 원하는 부분들을 수정해서 넣어보도록 하겠다.
    • 기존 : Code(회원가입 번호) , Name(이름)
    • 추가 : ID(아이디), passwd(패스워드)
  • 추가된 2가지를 갖고 제대로 저장이 되는지 , 찾기(불러오기)가 제대로 되는지 확인할 수 있는 테스트 코드를 만들도록 하겠다.

 

1) MemberRepository 수정

import HJproject.Hellospring.domain.Member;

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

public interface MemberRepository {
    Member save(Member member);

    Optional<Member> findByCode(Long code);
    Optional<Member> findByName(String name);
    Optional<Member> findById(String Id); // 아이디로 찾기 위해 추가
    Optional<Member> findBypasswd(String passwd); // 패스워드로 찾기 위해 추가

    List<Member> findAll(); // 모든 회원 리스트 반환

}

 

2) MemoryMemberRepository

import HJproject.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.setCode(++sequence); // store 에 저장 전 member 값에 sequence 값을 하나 올려서 넣음
        store.put(member.getCode(), member); // store 에 member와 code와 함께 저장

        return member;
    }

    @Override
    public Optional<Member> findByCode(Long code) {
        return Optional.ofNullable(store.get(code));
        // optional.ofnullable 를 사용하면 null 이 반환되더라도 감쌀 수 있음
        // 추후 클라이언트에서 활용 가능

    }

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

	//////// 여기서부터 //////////
    @Override
    public Optional<Member> findById(String id){ // id로 저장된 값을 찾아옴
        return store.values().stream() // store에 저장된 값(value)를 반환
                .filter(member -> member.getId().equals(id)) 
								// member 에서 meber.getid 를 통해 id 와 동일한 값을 필터
                .findAny(); // 전체에서 검색?
    }

    @Override
    public Optional<Member> findByPasswd(String passwd){
        return store.values().stream()
                .filter(member -> member.getPasswd().equals(passwd))
                .findAny();
    }
    
   //////// 여기까지 추가하였다 //////////


    public void clearStore(){ // 각각 테스트 진행 시 결과 초기화를 위한 메소드
        store.clear();
    }
}

 

3) MemoryMemberRepositoryTest

import HJproject.Hellospring.domain.Member;

import HJproject.Hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

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

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

class MomoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach // 하나의 메서드가 끝나면 AfterEach 메서드 동작
    public void AfterEach(){
        repository.clearStore();
    }

    @Test
    public void save(){
        Member member = new Member();
//        Member member2 = new Member();

        member.setName("java");
//        member2.setName("하늘");

        repository.save(member);
//        repository.save(member2);

//        System.out.println(member.getCode());
//        System.out.println(member.getName());

        Member result = repository.findByCode(member.getCode()).get();
        //  repository 안에 저장된 memeberCode 를 가져와서 result 에 저장

        // 기대값과 실제값을 확인하기 위해 Assertions 의 assertEquals 라는 메서드 활용

        // 검증 방법 1. Assertions.assertEquals(기대값, 실제값) => junit
//        Assertions.assertEquals(member, result);

        // 검증 방법 2. Assertions.assertThat(실제값).isEqualTo(기대값) => assertj
        assertThat(result).isEqualTo(member); // static import 상태

        System.out.println("저장되는 값이 확인되었습니다.");

    }

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

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

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

        assertThat(result).isEqualTo(member1);
//        System.out.println(member1.getCode() + " " + member1.getName());

        System.out.println("findByName 정상 동작 확인하였습니다");

    }

///// 여기서부터 //////

    @Test
    public void findById(){ // findById 메소드 동작 테스트
        Member member = new Member();
        member.setId("java_ID");

        repository.save(member);

       Member findId = repository.findById("java_ID").get();

        assertThat(findId).isEqualTo(member);

        System.out.println(member.getId());
        System.out.println("findById 정상 동작 확인");

    }

    @Test
    public void findByPasswd(){ // findByPasswd 메소드 동작 테스트
        Member member = new Member();
        member.setPasswd("java_passWD");

        repository.save(member);

        Member findPW = repository.findByPasswd("java_passWD").get();

        assertThat(findPW).isEqualTo(member); // 여기서 member 은 내가 방금 저장한 member

        System.out.println(member.getPasswd());
        System.out.println("findByPasswd 정상 작동 확인");

    }
    
///// 여기까지 추가 //////


    @Test
    public void findAll(){
        Member memeber1 = new Member();

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

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

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

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

        System.out.println("findAll 정상 동작 확인하였습니다");

    }
}

 

4) 만든 부분 검증하기

위 3가지 부분을 수정하여 단순히 회원 번호, 회원 이름뿐만 아니라 회원 ID 와 회원 Passwd 도 저장 및 찾기가 가능하도록 만들었다.

새로만든 findByid 와 Passwd가 정상 동작하는 것을 확인!

 

댓글