이 글은 인프런에서 김영한 님의 "스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술"을 수강 후 개인적으로 공부한 내용을 정리한 게시글입니다. 잘못된 점이나 부족한 부분이 있다면 언제든 지적 부탁드립니다.
- 비즈니스 요구사항 정리
- 회원 도메인과 리포지토리 만들기
- 회원 리포지토리 테스트 케이스 작성
- 회원 서비스 개발
- 회원 서비스 테스트
1. 비즈니스 요구사항 정리
데이터 : 회원ID, 이름
기능 : 회원 등록, 조회
아직 데이터 저장소가 선정되지 않음(가상의 시나리오) // DB 선정 X
컨트롤러 : 웹 MVC의 컨트롤러 역할
서비스 : 핵심 비즈니스 로직 구현, ex) 회원은 중복 가입이 안됨
리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리 , 회원객체 저장소
도메인 : 비즈니스 도메인 객체, ex) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨
2. 회원 도메인과 리포지토리 만들기
# 회원 도메인
domain : 패키지 > Member : 회원 객체 자바 파일
package hello.hellospring.domain;
public class Member {
private Long 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;
}
}
멤버 변수 : id, name
* id는 회원이 설정하는 것이 아니라 시스템이 데이터를 구분하기 위해 임의로 부여
* name은 회원이 회원가입 시 입력
각 멤버 변수는 getter와 setter를 가짐
# 회원 레포지토리의 인터페이스
repository : 패키지 > MememberRepository : 인터페이스
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();
}
save(Member member) : Member 객체를 저장
findById(Long id) : id로 Member 객체를 검색
findByName(String name) : name으로 Member 객체 검색
findAll() : 모든 회원 리스트 반환
* Optional은 Java8에 들어있는 기능으로, id나 name을 기준으로 Member 객체를 찾아 반환할 때 null이 반환되는것을 처리하기 위한 것
# 회원 리포지토리 메모리 구현체
repository : 패키지 > MemberRepository 인터페이스를 구현한 MemoryMemberRepository
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.*;
/**
* 동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용 고려
*/
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));
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
public void clearStore() {
store.clear();
}
}
store map : 구현체에서는 저장된 회원들의 id(key)와 Member 객체(value)를 맵으로 저장할 HashMap으로 구현
sequence : store의 id(key)값을 생성하는 변수
save(Member member) : Member객체를 받아 sequence를 1증가시키고 이를 id를 설정, Member의 id와 Member 객체를 store map에 저장하고 저장된 member 객체를 반환
findById(Long id) 함수 : store에서 id가 key값인 member객체를 Optional로 감싸 반환
findByName(String name) 함수 : 저장된 Member객체를 담고 있는 store map의 모든 value를 돌며 member객체의 name이 입력받은 name과 같다면 (filter) 그 Member객체를 반환
findAll() : store에 저장되어있는 모든 Member객체들을 List형태로 반환
clearStore() : 가입한 회원들의 저장소인 store의 내용을 모두 삭제
자바 공부를 한다고 했는데, 코드가 잘 이해가 안된다
자바 공부를 더 하고 오겠당 01/18 15:40분
~~ 돌아 왔다 ~~ 01/18 21:44분
다시 들으니까 이해가 된다 오예 22:00분
3. 회원 리포지토리 테스트 케이스 작성
: 방금 작성한 회원 리포지토리가 내가 원하는대로 동작할까? -> 테스트 케이스로 검증!
자바의 main 메서드를 통해 실행하거나, 웹 어플리케이션의 컨트롤러를 통해 실행 하는 방법
-> 준비하고 실행하는데 오래걸림
-> 여러 테스트를 한번에 실행하기 어려움
자바는 JUnit이라는 프레임워크로 테스트를 실행해 이러한 문제를 해결한다
test/java/프로젝트명 아래에 repository 폴더를 작성하고(위에서 작성한 폴더명과 동일) MemoryMemberRepositoryTest(위의 구현체 이름+Test) 파일을 생성한다.
save() 테스트
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class MemoryMemberRepositoryTest { // public으로 안해도 됨
MemberRepository repository = new MemoryMemberRepository();
@Test
public void save(){
Member member = new Member();
member.setName("zer0silver");
repository.save(member);
Member result = repository.findById(member.getId()).get();
// Optional에서 값을 꺼낼 때에는 get() 이용
// 방법 1
// Assertions.assertEquals(member, result);
// 방법 2
assertThat(member).isEqualTo(result);
}
static import 하는 방법
alt + enter
findByName() 테스트
@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);
}
shift + F6 : 같은 문구 동시 수정 가능
findAll() 테스트
@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);
}
테스트 순서는 알수없음! 그래서 테스트 하나가 끝나면 데이터를 clear 해줘야함!
@AfterEach
public void afterEach() {
repository.clearStore();
}
.
.
.
public void clearStore() {
store.clear();
}
@AfterEach : 한번에 여러 테스트를 실행하면 메모리 DB에 직전 테스트의 결과가 남을 수 있다.
이렇게 되면 다음 이전 테스트 때문에 다음 테스트가 실패할 가능성이 있다.
하나의 테스트가 종료될 때 마다 @AfterEach 실행. 여기서는 메모리 DB에 저장된 데이터를 삭제한다.
테스트는 각각 독립적으로 실행되어야 한다. 테스트 순서에 의존관계가 있는 것은 좋은 테스트가 아니다
테스트 코드 없이 개발은 혼자 할 때는 괜찮으나, 소스코드가 엄청 많아지거나, 여러명이 함께 개발할때는 테스트코드가 필수이다. 꼭 깊이있게 공부해보자
지금 방법은 구현 클래스 Repository를 작성 후, 맞는지 검증하는 과정으로 작성하였는데,
이것과 반대로 Test Class를 먼저 작성 후(틀 먼저) MemberMemoryRepository를 작성할 수도 있다.
이 방법을 TDD(Test-driven Development), 즉 테스트 주도 개발 이라고 한다.
출처
인프런 스프링 입문 - 김영한
'Server > Spring boot' 카테고리의 다른 글
[스프링] 인프런 스프링 입문 #회원 관리 예제 - 웹 MVC 개발 (0) | 2022.01.30 |
---|---|
[스프링] 인프런 스프링 입문 #스프링 빈과 의존관계 (0) | 2022.01.30 |
[스프링] 인프런 스프링 입문 #회원 관리 예제 - 백엔드 개발 #2 (0) | 2022.01.19 |
[스프링] 인프런 스프링 입문 #스프링 웹개발 기초 (0) | 2022.01.18 |
[스프링] 인프런 스프링 입문 #프로젝트 환경설정 (0) | 2022.01.18 |