출처) 인프런 스프링 입문 강의
단순한 회원 시나리오를 가정함
일반적인 웹 애플리케이션 구조

클래스 의존 관계
회원 서비스가 회원 레포지토리 (인터페이스)에 의존
아직 저장소가 지정되지 않았기 때문에 인터페이스로 생성
나중에 구현 클래스를 변경할 수 있게 하기 위함
초기 개발 단계에서는 개발을 위해 메모리 기반 데이터 저장소 사용
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;
}
}
Optional: iD, name으로 조회시 반환 값이 없는 경우 null이 아니라 Optional로 감싼 것을 반환함, Java 8의 기능
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); // id로 회원 조회
Optional<Member> findByName(String name); // name으로 조회
List<Member> findAll(); // 지금까지 저장된 모든 회원을 조회
}
구현체 생성하기
repository 패키지에 MemoryMemberRepository 클래스 생성
implements MemberRepository 하고
Option + enter 키 누르면

implements methods가 나옴 클릭 후 모든 메소드 선택하기
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public class MemoryMemberRepository implements MemberRepository{
@Override
public Member save(Member member) {
return null;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.empty();
}
@Override
public Optional<Member> findByName(String name) {
return Optional.empty();
}
@Override
public List<Member> findAll() {
return null;
}
}
이제 구현 해봅시다 ~
Map을 사용하여서 저장 Key = 회원 아이디, Value = 회원
⭐️ 단, 실무에서는 공유 변수는 ConcurrentHashMap을 사용해야한다! 예제이므로 간단히
sequence는 id 값을 생성해주는 역할
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
save(Member member)
setId로 sequence값 +1하고 id 셋팅 (id는 유저한테 입력 받는게 아니라, 시스템에서 결정)
store에 (id, member) 맵 저장
저장된 결과 반환
public Member save(Member member) { // id 시스템에 저장
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
findByID(Long id)
id 입력 받으면 store에 저장된 정보를 조회
null을 받을 수 있으므로, Optional.ofNullable()로 감싸기
이렇게 감싸면 클라이언트 측에서 처리를 할 수 있음
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
findByName(String name)
store에 있는 값을 돌면서 member의 getName 반환 값이 요청 받은 name과
동일한 경우 나오는 결과 값을 반환
만약 끝까지 돌았는데 아무것도 없다면 Optional에 null이 포함되어 반환됨
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
findAll()
실무에서 리스트 형태 많이 사용함
store에 저장된 값 (member 값)을 list 형태로 만들어서 리턴하기
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
전체 코드
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) { // id 시스템에 저장
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) { // id로 member 조회
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) { // name으로 member 조회
return store.values().stream() // store에 저장된 값 돌면서, name이 일치하는 값 리턴
.filter(member -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
}
위에서 구현한 회원 기능이 잘 되는지 테스트 해보기 위해서 테스트 케이스를 작성
자바에서 개발 기능을 테스트할 때 main 메소드, 웹 애플리케이션 컨틀롤러를 통해 실행하는데 이렇게 하는 경우는 반복 실행 및 여러 테스트를 한 번에 하기 번거로움
➡️ JUnit 프레임 워크로 테스트 실행
src/test/hello.hellospring 폴더 아래에
MemoryMemberRepositoryTest 클래스 생성
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
}
}
save 테스트
@Test
public void save(){
Member member = new Member();
member.setName("채원");
repository.save(member);
Member result = repository.findById(member.getId()).get();
// 출력해서 확인하기
// System.out.println("result = " + (result == member));
// Junit 사용해서 확인하기
assertThat(member).isEqualTo(result);
}
새 회원정보를 저장했을 때
findById를 통해 찾은 회원 정보가 저장한 정보 (member)와 같은지
확인함
Test 파일을 실행시키면
System.out.println() 을 통해서 결과 값을 확인할 수 있음

그러나 이렇게 출력하면서 확인하는 건 번거롭기 때문에 ~
Assertions를 사용함, assertEquals 보다 assertThat().isEqualTo()가
더 직관적임
import static org.assertj.core.api.Assertions.*;
이걸 import 해주면 앞에 Assertions 빼고 사용해도 됨
assertThat(member).isEqualTo(result);
아래에서
member가 result과 같은지 확인
println 사용할 때랑 달리 별 다른 출력은 없지만 초록불 잘 들어옴

만약 isEqualTo에 null을 넣어보면 오류가 발생하는 것을 확인할 수 있음

findByName 테스트
정확한 테스트를 위해 회원 정보를 2개 저장하고, 테스트해보겠음
채원, 채이 회원이 있을 때 채원 회원의 이름으로 멤버 정보를 조회하면 잘 되는지 확인
@Test
public void findByName(){
// 회원 정보 저장
Member member1 = new Member();
member1.setName("채원");
repository.save(member1);
Member member2 = new Member();
member2.setName("채이");
repository.save(member2);
// 이름으로 회원 정보 조회한 결과
Member result = repository.findByName("채원").get();
System.out.println(result.getName());
assertThat(result).isEqualTo(member1);
}
assertThat을 확인도 문제 없고, 실제로 result (이름으로 찾은 회원 정보)에서 getName()을 했을 때 채원 이름이 잘 나온 것을 확인할 수 있음

여기서 만약 "채이" 즉 회원2번의 이름으로 정보를 조회했을 때, member1 "채원"과 일치하는지 확인하면 당연히 불일치해서 오류가 발생함

이렇게 Test 하는 방식의 장점 = 한 번에 여러 테스트 돌릴 수 있음
hello.helloSpring 즉 클래스 레벨에서 실행 시키면 여러 테스트가 한 번에 돌아감
findAll 테스트
위처럼 2개의 계정을 만들어 놓고, findAll 했을 때 찾은게 2개인지 확인하기
@Test
public void findAll(){
Member member1 = new Member();
member1.setName("채원");
repository.save(member1);
Member member2 = new Member();
member2.setName("채이");
repository.save(member2);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(2);
}
회원 생성할 때 같은 이름으로 만들면
findAll이 먼저 실행되면 findByName에서는 findAll에서 만든 값을 찾아와버림
그래서 오류가 발생할 수 있음 !!

⭐️ Test를 짤 때는 순서관계 (의존) 없이 돌아갈 수 있어야함
➡️ 테스트가 끝나면 정보를 삭제해주는 것이 필요
/main/java/hello.hellospring/repository/MemberMemoryRepository
구현체에다가 store에 저장된 걸 다 지우는 메서드를 추가함
public void clearStore(){
store.clear();
}
그리고 test 파일로 다시 돌아와서 아래 코드 추가
@AfterEach
public void afterEach(){
repository.clearStore();
}
한 테스트 수행이 끝날 때마다 호출되는 일종의 콜백 개념
AfterEach를 이용해서 store에 저장된 걸 지워줌

3가지 테스트 모두 잘 작동하는 것을 확인