회원의 데이터: 회원ID, 이름
기능: 회원 등록 및 조회
일단 DB를 선택하지 않은 상황이라고 가정한다.
💡 회원ID는 시스템에서 설정하도록 한다.
컨트롤러: MVC에서의 컨트롤러
서비스: 비즈니스 로직을 구현
리포지토리: DB접근, 도메인 객체를 DB에 저장 및 관리
도메인: 비즈니스 도메인 객체. ex) 회원, 주문 등 주로 DB에 저장되고 관리되는 것
전체 프로젝트 폴더 구조는 다음과 같이 구성된다.

DB를 선정하지 않았으므로 리포지토리는 우선 interface를 만들고, 구현체는 메모리 구현체로 만들도록 한다. (나중에 DB가 선정이 된다면 구현 클래스를 바꿔주어야 하므로 interface가 필요하다.)


- 구현체 기능
- 회원 등록
- id로 조회
- 이름으로 조회
- 전체 회원 조회
- 전체 회원 삭제
개발한 기능을 테스트하는 방법은 여러가지가 존재한다.
이러한 방법 중에는 main 메서드로 실행을 하거나 컨트롤러를 통해 해당 기능을 실행하는 방법이 있다.
그러나 이 방법은 여러 단점이 존재하는데, 1. 준비하고 실행하는 것에 있어 오래 걸리는 문제 2. 반복 실행하기 어렵다는 문제 3. 여러 테스트를 한꺼번에 실행하기 어렵다는 단점이 있다. 따라서 자바의 JUnit이라는 프레임워크로 테스트를 진행하는 것을 통해 단점을 해결할 수 있다.
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
//import org.junit.jupiter.api.Assertions;
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); //이때 자동으로 id가 세팅됨.
Member result = repository.findById(member.getId()).get();
//System.out.println("result = " + (member == result));
//Assertions.assertEquals(member, result); // 순서는 expected, actual
//Assertions.assertThat(member).isEqualTo(result);
assertThat(member).isEqualTo(result); // Assersions를 static import를 했을 경우
}
@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);
}
}
테스트는 서로 순서와 의존 관계가 없이 실행되어야 한다. 따라서 하나의 테스트가 끝날 때마다 저장소나 공용 데이터를 지워주어야 문제가 생기지 않는다.
그러므로 @AfterEach를 사용해 하나의 메서드가 끝날 때마다 호출되는 콜백 메서드 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 = new MemoryMemberRepository();
//이렇게 하게되면 MemberServiceTest와 다른 memberRepository 객체를 생성하여 테스트를 하게 되는 문제가 발생함.
//따라서 아래와 같이 수정
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository){ //생성자 사용, 이러한 것을 Dependency Injection이라고 한다.
this.memberRepository = memberRepository;
}
/**
* 회원 가입
* **/
public Long join(Member member){
// 같은 이름이 있는 중복 회원X
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의 객체를 새로 생성하게 되면 차후 작성할 회원 서비스 테스트 클래스(MemberServiceTest class)와 다른 객체를 생성하여 테스트하게 되므로 생성자를 통해 매개변수를 받아 객체를 생성하도록 한다. 이는 Dependency Injection이라고 한다.
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
//MemberService memberService = new MemberService();
//MemoryMemberRepository MemberRepository = new MemoryMemberRepository();
//MemberService에서 만든 객체와 다른 객체를 생성하여 테스트하는 것이므로 문제가 있음
//수정
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();
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);
/* 방법1: try, catch 사용
try {
memberService.join(member2);
fail();
}catch (IllegalStateException e){
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
*/
//방법2
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
// () -> memberService.join(member2) 로직 실행 시 IllegalStateException 예외가 터져야 한다.
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); //메시지 검증
//then
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
이처럼 @BeforeEach를 사용해 테스트를 실행하기 전 MemoryMemberRepository 객체 생성 후 MemberService 객체를 생성하는 것을 통해 동일한 MemberRepository 객체를 생성하도록 한다.
본 포스팅은 inflearn 김영한 강사님의 "스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술" 강의에 기반하여 작성되었습니다.
강의 링크