

package hello.hellospring.domain;
public class Member {
private Long id; //아이디 식별자(데이터 구분을 위해 시스템이 정하는 임의의 값)
private String name; //이름(회원이 직접 입력한 값)
//getter, setter 효율에 대한 얘기가 많은데 일단 쉬운 문제기 때문에 단순하게 사용
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;
}
}
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 : NPE(NullPointerException) 처리를 도와주는 Wrapper Class
Optional<Member> findById(Long id); //id로 저장소에서 회원 찾기
Optional<Member> findByName(String name); //name으로 저장소에서 회원 찾기
List<Member> findAll(); //지금까지 저장된 모든 회원 리스트를 반환
}
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.*;
public class MemoryMemberRepository implements MemberRepository{ //구현체
//원래는 동시성 문제가 있을 수 있어서 공유되는 변수일 때는 ConcurrentHashMap을 써야하지만
//간단한 예제이므로 HashMap으로 설정
private static Map<Long, Member> store = new HashMap<>();
//실무에서는 동시성 문제로 AtomicLong() Wrapping 클래스를 사용
private static long sequence = 0L; //키 값을 생성
@Override
public Member save(Member member) {
member.setId(++sequence); //id 셋팅
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
//결과가 없을(null) 수 있으니 Optional로 감싸기
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny(); //member.getName()이 파라미터로 넘어온 name과 같은 경우 반환
} //끝까지 찾았는데 없으면 Optional에 null이 포함돼서 반환
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values()); //store Map의 value값(=Memeber) 반환
}
public void clearStore() { //테스트 케이스에서 사용(후술)
store.clear();
}
}
System.out.println()package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@Test
public void findById() { //동작하는지 확인
Member member = new Member();
member.setName("spring");
repository.save(member); //member를 저장하면 id가 자동 셋팅
Member result = repository.findById(member.getId()).get();
System.out.println("result = " + (result == member));
}
}

❓ 만약 같지 않다면
Assertions.assertEquals()Assertions : org.junit.jupiter.apipackage hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@Test
public void findById() { //동작하는지 확인
Member member = new Member();
member.setName("spring");
repository.save(member); //member를 저장하면 id가 자동 셋팅
Member result = repository.findById(member.getId()).get();
Assertions.assertEquals(member, result); // member와 result가 똑같은지 확인
}
}

❓ 만약 같지 않다면
Assertions.assertThat()Assertions : org.assertj.core.apipackage hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@Test
public void findById() { //동작하는지 확인
Member member = new Member();
member.setName("spring");
repository.save(member); //member를 저장하면 id가 자동 셋팅
Member result = repository.findById(member.getId()).get();
Assertions.assertThat(result).isEqualTo(member);
}
}

❓ 만약 같지 않다면
❗ Assertions를 import static으로 설정하면 다음부터 생략 가능
package hello.hellospring.repository; import hello.hellospring.domain.Member; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.*; //import static으로 설정 public class MemoryMemberRepositoryTest { MemoryMemberRepository repository = new MemoryMemberRepository(); @Test public void findById() { Member member = new Member(); member.setName("spring"); repository.save(member); Member result = repository.findById(member.getId()).get(); assertThat(result).isEqualTo(member); //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); //간단히 size만 검증
}

❓ 만약 같지 않다면
: Test Case의 모든 class를 실행해보면 오류가 뜸

원인
Test 순서는 보장이 안되기 때문에 모든 테스트는 순서와 상관 없이 메서드 별로 따로 동작하게 설계해야 함 => 순서에 의존적으로 설계하면 안됨
위에서 확인할 수 있듯 findAll() -> findById() -> findByName() 순으로 테스트가 진행되었는데, findAll()에서 이미 spring1, spring2가 저장되었기 때문에 findByName()에서 이전에 저장한 객체가 호출되는 것.
❗ 하나의 Test가 끝날 때 마다 Repository를 깔끔하게 지워주는 코드를 넣어야 함
MemoryMemberRepository class에 clearStore() 메서드 추가 public void clearStore() {
store.clear();
}
Step2. MemoryMemberRepositoryTest class에 afterEach() 메서드 추가
@AfterEach //각 메서드 실행이 끝날때 마다 동작을 하는 것. 일종의 call back 메서드
public void afterEach() {
repository.clearStore();
}
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach //메서드 실행이 끝날때 마다 동작을 하는 것. 일종의 call back 메서드
public void afterEach() {
repository.clearStore();
}
@Test
public void findById() { //동작하는지 확인
Member member = new Member();
member.setName("spring");
repository.save(member); //member를 저장하면 id가 자동 셋팅
Member result = repository.findById(member.getId()).get();
assertThat(result).isEqualTo(member);
}
@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);
}
}

package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import java.util.Optional;
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
public Long join(Member member) {//회원가입
//같은 이름이 있는 중복 회원 허용X
Optional<Member> result = memberRepository.findByName(member.getName());
//null이 아닌 값이 있으면
result.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
memberRepository.save(member);
return member.getId();
}
}
Optional<Member> 생략package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
public Long join(Member member) {//회원가입
//같은 이름이 있는 중복 회원 허용X]
//findByName 결과가 Optional<Member>이므로 Optional 생략 가능
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다");
});
memberRepository.save(member);
return member.getId();
}
}
throw new 부분 따로 메서드로 빼기package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
public Long join(Member member) {//회원가입
validateDuplicateMember(member); //중복 회원 검증
memberRepository.save(member); //통과하면 저장
return member.getId();
}
private void validateDuplicateMember(Member member) { //같은 이름이 있는 중복 회원 허용X
//findByName 결과가 Optional<Member>이므로 Optional 생략 가능
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다");
});
}
}
❗ 리팩토링 단축키 : Ctrl + Alt + Shift + T
MemberService class 최종 코드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();
public Long join(Member member) {//회원가입
validateDuplicateMember(member); //중복 회원 검증
memberRepository.save(member); //통과하면 저장
return member.getId();
}
private void validateDuplicateMember(Member member) { //같은 이름이 있는 중복 회원 허용X
//findByName() 결과가 Optional<Member>이므로 Optional 생략 가능
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다");
});
}
public List<Member> findMembers() { //전체 회원 조회
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId) { //회원 id 조회
return memberRepository.findById(memberId);
}
}
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class MemberServiceTest {
MemberService memberService = new MemberService();
@Test
void 회원가입() { //test는 한글로 바꿔도 무관
//given : 이런 상황이 주어져서(어떤 데이터를 기반으로 하는지)
Member member = new Member();
member.setName("hello");
//when : 뭔가를 실행했을 때(어떤 것을 검증하는지)
Long saveId = memberService.join(member); //return값은 저장한 아이디
//then : 결과가 이렇게 나온다(어디가 검증부인지)
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
}
try-catch @Test
public void 중복_회원_예외() {
//given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring"); //member1과 이름 동일
//when
memberService.join(member1);
try {
//spring 이름이 있는 상태에서 중복으로 가입했을 때
memberService.join(member2);
/*
* 강의에서는 실제 발생할 예외가 발생하지 않을 경우를 방지하기 위해 사용했으나
* 22년 기준 fail() 기능이 작동하지 않음
* 여기서 어떻게 해야할지 추가로 찾아봐야 함
*/
//fail();
} catch (IllegalStateException e) {
//실제 예외 발생 시 나오는 메세지가 MemberService Class에서 설정한 메세지대로 나오는지 확인
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다");
}
}

❓ 만약 메세지가 같지 않다면
assertThrows() @Test
public void 중복_회원_예외() {
//given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring"); //member1과 이름 동일
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
}

❓ 만약 메세지가 같지 않다면
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*; //static 없으면 assertThrows 오류남
class MemberServiceTest {
MemberService memberService = new MemberService();
MemoryMemberRepository memberRepository = new MemoryMemberRepository();
@AfterEach
public void afterEach() { //memory clear
memberRepository.clearStore();
}
@Test
void 회원가입() { //test는 한글로 바꿔도 무관
//given : 이런 상황이 주어져서(어떤 데이터를 기반으로 하는지)
Member member = new Member();
//afterEach 메서드 제대로 동작하는지 확인하기 위해 테스트 할 이름 통일
member.setName("spring");
//when : 뭔가를 실행했을 때(어떤 것을 검증하는지)
Long saveId = memberService.join(member); //return값은 저장한 아이디
//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"); //member1과 이름 동일
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
}
}

: 현재 MemberService class에 있는 memberRepository와 MemberServiceTest class에 있는 memberRepository객체는 각각 new로 생성되어 서로 다른 객체.
MemberService class의memberRepository
MemberServiceTest class의memberRepository
지금은 MemoryMemberRepository class를 static으로 선언해서 문제가 없지만 static이 빠지면 문제가 생기며, static이 있다고 해도 굳이 다른 객체로 쓸 이유가 없음. 애초에 같은 객체로 테스트를 해야하기 때문에 같은 인스턴스를 쓰게끔 설계해야 함.
해결법 : MemberService class에서 직접 객체를 생성하지 않고 외부에서 넣어주도록 바꿈
=> DI(Dependency Injection) 의존성 주입
Step 1.
MemberService class의memberRepository수정public class MemberService { //비즈니스 처리 private final MemberRepository memberRepository; public MemberService(MemberRepository memberRepository) { //DI(Dependency Injection) 의존성 주입 this.memberRepository = memberRepository; }
Step 2.
MemberServiceTest class에 있는memberRepository수정class MemberServiceTest { MemberService memberService; MemoryMemberRepository memberRepository; //동작 하기 전에 넣어줘야 함 @BeforeEach public void beforeEach() { //각 테스트 실행하기 전에 객체를 각각 생성 memberRepository = new MemoryMemberRepository(); memberService = new MemberService(memberRepository); }
최종코드
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
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.*;
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() { //memory clear
memberRepository.clearStore();
}
@Test
void 회원가입() { //test는 한글로 바꿔도 무관
//given : 이런 상황이 주어져서(어떤 데이터를 기반으로 하는지)
Member member = new Member();
//afterEach 메서드 제대로 동작하는지 확인하기 위해 테스트 할 이름 통일
member.setName("spring");
//when : 뭔가를 실행했을 때(어떤 것을 검증하는지)
Long saveId = memberService.join(member); //return값은 저장한 아이디
//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"); //member1과 이름 동일
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
}
}

