
데이터: 회원ID, 이름
기능: 회원 등록, 조회
아직 데이터 저장소가 선정되지 않았다고 가정

컨트롤러: 웹 MVC의 컨트롤러 역할
서비스: 핵심 비즈니스 로직 구현
리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
도메인: 비즈니스 도메인 객체. ex)회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨

아직 데이터 저장소가 선정되지 않아서 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계
데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정
초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용
id, name 변수 생성
Alt+Enter-> getter/setter -> getId, setId, getName, setName 메소드 생성
src/main/java/hello.hellospring/domain/member.java
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;
}
}
Member 클래스에서 save 객체 생성
Optional과 List를 사용하여 id, name 탐색
src/main/java/hello.hellospring/repository/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();
}
Alt+Enter-> Implement methods -> 메소드 오버라이딩
key와 value로 이루어진 자료구조 Hashmap store
key 값을 생성해주는 변수 sequence
key = member.getId() / value = member
src/main/java/hello.hellospring/repository/MemoryMemberRepository
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)); //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());
}
}
개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다.
이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다.
자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.
@Test어노테이션을 작성하면 테스트 유닛이 생성됩니다.
@AfterEach를 사용하면 각 테스트가 종료될 때 마다 이 기능을 실행한다. 여기서는 메모리 DB에 저장된 데이터를 삭제한다. (clearStore메소드)
테스트는 각각 독립적으로 실행되어야 하며 테스트 순서에 의존관계가 있는 것은 좋은 테스트가 아니다.
save Member 객체를 생성, name을 spring으로 설정하고 리포지토리에 생성된 Member 객체를 save함수로 저장합니다.
member에 저장된 id로 findById함수를 통해 레포지토리에 저장된 Member객체를 꺼내고(get()), 꺼낸 Member 객체(result)가 save를 통해 저장했던 member와 동일한 객체인지 검증합니다.
findByName 동작을 테스트 하기 위해 새로운 Member객체 2개를 생성합니다. 각각의 객체는 member1, member2이며, name은 spring1, spring2로 세팅합니다.
생성된 두 Member객체를 save함수를 이용해 리포지토리에 저장합니다.
리포지토리에서 spring1이 name인 Member객체를 검색하기 위해 findByName함수를 사용합니다.
findAll 함수의 동작을 테스트 하기 위해 Member객체를 2개 이상 생성(member1, member2), name을 각각 spring1, spring2로 세팅 후 save함수를 이용해 레포지토리에 저장합니다.
findAll함수를 이용해 리포지토리에 저장된 모든 회원 객체 정보를 리스트 형태로 반환받습니다.
반환받은 리스트(result)에 들어있는 멤버 수가 총 2개인지 검증합니다.
src/test/java/hello.hellospring/repository/MemoryMemberRepository
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.*;
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();
assertThat(member).isEqualTo(result); //alt+enter로 static import해서 Assertions안써도됨
}
@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();
assertThat(result).isEqualTo(member1);
}
@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();
assertThat(result.size()).isEqualTo(2);
}
}
비즈니스 로직을 구현하는 회원 서비스를 개발합니다
리포지토리에서는 '저장(save)', '검색(findBy)'와 같은 데이터를 DB에 넣고 빼는 작업이 구현되었고, 서비스에서는 '회원가입', '전체 회원 조회'와 같은 비즈니스 작업을 구현합니다
리포지토리에서는 직관적인 함수명
서비스에서는 비즈니스 용어 등 비즈니스와 연관되도록
join은 회원가입을 수행하는 함수입니다. 리포지토리에 member 객체를 save함수로 저장하고 생성된 회원의 id를 반환합니다.
validateDuplicateMember는 리포지토리에 이미 같은 이름으로 등록된 회원이 있는지 확인 후 같은 이름을 가진 회원이 존재한다면 에러메세지를 예외로 처리하고 중단하는 함수입니다.
findMembers함수는 findAll 함수를 사용하여 리스트 형태로 저장소에 등록된 모든 회원 정보를 반환합니다.
findOne함수는 member의 id를 가지고 특정한 한 회원을 조회합니다. 레포지토리 findByld 함수를 사용해 반환된 Member객체를 Optional로 감싸 반환합니다.
src/main/java/hello.hellospring/service/MemberService
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.assertj.core.api.Assertions.assertThat;
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("woojin");
//when
Long saveId = memberService.join(member);
//then
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
public void 중복_회원_예약() {
//given
Member member1 = new Member();
member1.setName("woojin");
Member member2 = new Member();
member2.setName("woojin");
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
/*
try {
memberService.join(member2);
fail();
} catch(IllegalStateException e) {
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
*/
//then
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
테스트 클래스 자동 생성:
Ctrl+Shift+T-> Create Test -> 선택 후 OK
테스트 코드의 경우 메소드를 알아보기 쉽도록 한글로 작성해도 된다. (빌드시 포함 X)
회원가입()은 서비스의 join함수의 회원가입을 검증하는 테스트입니다.
memberService의 join을 사용해 member 객체를 넘겨 회원가입을 진행하고 saveId로 회원가입 된 객체의 id를 반환 받습니다.
리포지토리의 findById를 사용해 가입 후 받은 id인 saveId로 찾은 Member 객체(findMember)의 이름과 woojin이 동일한지 검사합니다.
중복_회원_예외() 는 중복회원이 회원가입을 시도할 때 정상적으로 에러 메세지를 출력하는지 검증하는 테스트입니다.
테스트를 수행하기 위해 이름이 woojin으로 같은 Member 객체 member1, member2를 생성합니다.
memberService의 join을 이용해 member1이 회원 가입합니다.
assertThrows를 사용하면 특정 로직을 실행 시 그에 해당하는 특정한 예외가 발생하는지를 검증할 수 있습니다.
이를 사용하여 member1과 같은 이름을 가진 member2가 회원가입을 시도하는 로직을 실행할 때, IllegalStateException의 예외가 발생하는지 검증합니다.(단순히 try ~ catch문을 사용하여 확인할 수도 있습니다.) 반환된 예외를 e로 받아 예외 e의 메시지가 터져야 하는 에러의 메시지와 동일한 메시지인지를 확인합니다.
src/test/java/hello.hellospring/service/MemberServiceTest
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.assertj.core.api.Assertions.assertThat;
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("woojin");
//when
Long saveId = memberService.join(member);
//then
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
public void 중복_회원_예약() {
//given
Member member1 = new Member();
member1.setName("woojin");
Member member2 = new Member();
member2.setName("woojin");
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
/*
try {
memberService.join(member2);
fail();
} catch(IllegalStateException e) {
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
*/
//then
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}