4. 회원 관리 예제 - 백엔드 개발
1) 비즈니스 요구사항 정리
비즈니스 요구사항 정리
- 데이터 : 회원 ID, 이름
- 기능 : 회원 등록, 조회
- 아직 DB가 선정되지 않았음을 가정하고 개발
일반적인 웹 애플리케이션 계층 구조
- 컨트롤러: 웹 MVC의 컨트롤러 역할, API 생성 시 컨트롤러 역할
- 서비스 : 서비스 클래스의 핵심 비즈니스 로직이 들어가 있음 (ex. 회원 가입 중복 불가)
- 도메인 : DB에 주로 저장되고 관리되는 (ex. 회원, 주문, 쿠폰 ...) 비즈니스 도메인 객체
- 리포지토리 : DB에 접근, 도메인 객체를 DB에 저장하고 관리
- 비즈니스 도메인 객체를 가지고 핵심 비즈니스 로직이 돌아가도록 구현한 계층
클래스 의존관계
- 아직 DB가 선정되지 않음 → 인터페이스로 설계 (회원 리포지토리에) → 메모리 구현체로 생성
- 메모리 구현체 : 비교적 간단하게 생성 가능, 메모리에 삽입 삭제 가능 (가벼운 메모리 기반 데이터 저장소)
- 구현체 = 인터페이스를 구현한 클래스
- [interface MemberRepository] -- [class MemoryMemberRepository implements MemberRepository] 가 있을 때, MemoryMemberRepository 는 MemberRepository 인터페이스를 구현한 구현체
- 추후에 DB가 선정되면 바꿔끼우기 위해 인터페이스가 필요함 (구현 클래스 변경 가능)
- 확장에 대한 요구사항 (변경에 닫혀있고, 확장엔 열려있게 설계) → 객체간 느슨한 결합 필요 ⇒ 인터페이스
- 메모리 구현체 : 비교적 간단하게 생성 가능, 메모리에 삽입 삭제 가능 (가벼운 메모리 기반 데이터 저장소)
2) 회원 도메인과 리포지토리 만들기
1. 회원 객체 생성
- 맴버 변수로 id, name 선언
- 이때 id는 시스템에서 데이터 식별 위해 임의로 부여하는 숫자
2. 회원 리포지토리 인터페이스 생성
4가지 기능 구현 in repository
- save : 회원을 저장소에 저장하는 기능.
- findById : save로 저장된 회원 정보를 id로 찾아서 반환하는 기능
- findByName : save로 저장된 회원 정보를 name으로 찾아서 반환하는 기능
- findAll : 지금까지 저장된 회원 리스트를 전부 반환
Optional
- Java 8에 들어가는 기능
- null 반환 시, null을 반환하는 방식 중 하나
- null을 그대로 반환하는게 아닌, 포장해서 반환하는 기능
3. 회원 리포지토리 메모리 구현체 생성
Alt + Shift + Enter를 통해 implements가 손쉽게 가능하다.
실무에서는 동시성 문제가 발생 가능함
➔ 공유 되는 변수일 경우 HashMap 대신 ConcurrentHashMap 사용해야 함
➔ 마찬가지로 sequence에서 long 대신 atumnlong 등으로 대체 필요
Optional.ofNullable
- null값이 return될 경우 감싸서 반환 해줌 → Client에서 뭐 해줌(?)
Stream 생성 → 필터 (조건에 부합하는 데이터 거름) → 찾음 (findAny() = 병렬처리 때 먼저 찾아진 요소 반환)
⇒ 하나 찾는 순간 바로 반환 , (if: 다 돌렸는데도 일치하는게 없음 = Optional 에 null이 포함 돼 반환)
(+ Stream이 익숙치 않아 추가적으로 알아보았다 >> https://c-omealong.tistory.com/23)
♘Lambda
- 형식 : 인터페이스명 메소드명 = (파라미터 명) -> { 수행할 코드 }
- 메소드를 정의하지 않고도 메소드처럼 활용 가능
- 주로 일회성으로 사용할 경우
- 생성은 Map이지만 반환은 List로 함 (∵ 실무에는 List가 사용 多)
3) 회원 리포지토리 테스트 케이스 작성
✔ 생성한 Member Repository 클래스가 내가 원하는대로 정상 작동 하는지 검증
→ 테스트 케이스 작성해서 검증 (코드를 코드로 검증)
회원 리포지토리 메모리 구현체 테스트
- 개발한 기능 테스트 : 자바의 main 메서드 or 웹 어플리케이션 컨트롤러 통해 해당 기능 실행하며 테스트
→ 실행 소요시간 큼 + 반복 실행 어려움 + 여러 테스트 한번에 실행 어려움
➔ JUnit (프레임워크)로 테스트 코드 만들어서 테스트 코드 자체를 실행함으로써 해결 가능 (in JAVA)
test/java/practice~/ 에 "테스트 할 패키지와 동일한 이름"+Test로 패키지, 클래스 생성
+) 굳이 public 설정할 필요 없음
Ⅰ) save 메서드 정상 동작 테스트
- org.junit.jupiter.api.Test을 import 해주면 save 메서드 실행 가능 → 동작 테스트 가능
1. 테스트 할 save 메서드, 임의의 회원 객체 생성
✔ 코드 자동완성 단축기 = Ctrl + Shift + Enter
✔ 이때, returnType (반환 타입)이 Optional임 → Optional에서 get메서드 이용해 값 꺼내 result에 저장
2. 검증하기 - (1) println() 이용
- new Member로 생성하면서 저장한 데이터와 / DB에서 꺼내온 데이터가 동일한지 비교한 결과 확인
➔ 가져온 result와 member가 같은 경우 (정상 작동) True 출력
But 일일히 확인하기란 사실상 불가능하다
2. 검증하기 - (2) Assertions 이용 [ org.junit.jupiter의 api ]
✔ Assertions.assertEquals("Expected", "Actual");
- Assertions (junit)의 assertEqual 메서드를 사용
- Expected : 기대하는 것 (비교할 기준)
- Actual : 실제 비교할 값
∴ new Member()로 생성해 저장한 객체(Expected) == findById 로 찾은 객체 (Actual)
✔ 만약 비교 대상이 같지 않다면 오류가 이렇게 표시된다 (서로 다른 Expected, Actual을 보여준다)
2. 검증하기 - (3) Assertions 이용 [ org.assertj.core의 api ]
✔ Assertions.assertThat("Actual").isEqualTo("Expected");
- Assertions (assertj)의 assertThat(A).isEqualTo(B) 메서드 이용
- A가 B랑 일치하는지 확인 ∴ A = 비교할 것 / B = 비교 대상
✔ Alt + Enter ⇒ static import 가능 (이후 assertThat만 바로 이용 가능)
==> 빌드 툴에서 빌드시, 테스트 케이스를 통과하지 않으면 다음단계로 넘어가는 것이 방지됨
Ⅱ) findByName 메서드 정상 동작 테스트
1. 테스트 할 findByName 메서드 생성
2. 비교할 객체 member1, member2 생성 후 name할당 → repository에 저장
✔ Rename 단축키 = Shift + F6
3. 검증하기 - assertion.assertThat()
result(="spring1"이 name인 member) 과 member1이 동일
✔ 만약 비교 대상이 같지 않다면("spring2"로 findByName 실행하므로 member2 저장)
⁂ Test case의 장점
- (1) save, (2) findById 를 동시에 테스트 가능 : 한번에 여러 메서드 동시에 테스트 가능
(Test클래스 레벨에서 Run하거나, Run Tests in practice~ 에서 실행하면 전체 class 테스트 가능)
Ⅲ) findAll 메서드 정상 동작 테스트
✔ 저장된 전체 회원객체를 불러와 List에 저장하고, 그 길이를 비교함으로써 검증 가능
✔ 기준 변수 추출 단축키 = Ctrl + Alt + v
But 다시 테스트 클래스에 대해 Run 할 경우 에러 발생
➔ member의 name이 중복 되기 때문! ("spring1", "spring2")
테스트 메서드는 순서 보장 X (∵ 모든 메서드 케이스는 생성 순서 상관없이 메서드 별로 병렬 수행 처리됨)
각 메서드 테스트 실행 후 repository를 주기적으로 초기화 시켜줄 메서드인 AfterEach에 대해 정의해야 함!
(Ex. findAll() >> AfterEach() >> findByName() >> AfterEach() >> save() 순서대로 실행)
+ MemoryMemberRepository 클래스에 clearStore 메서드 정의 ⇒ 메모리(store) 초기화 기능 추가
이후 실행 시켜주면 오류 없이 작동한다.
♘ 테스트 케이스 작성시 유의할 점
- 테스트는 서로 의존관계없이 설계돼야 함
∴ 테스트가 끝날 때마다 저장소 or 전역 변수 (=공유 데이터)를 초기화 해주는 메서드 필수
+)
- 테스트 케이스(=틀)을 먼저 생성한 뒤, 거기에 맞는지 테스트 해가며 진행하는 개발 방식도 존재
- 테스트 주도 개발 (TDD) : [선] 테스트 → [후] 구현 클래스 생성
- 테스트 개수가 많을 경우
- 빌드 or 최상위 레벨에서 Run Tests... 실행 or gradle
'Spring > Spring boot 입문' 카테고리의 다른 글
[스프링 부트] 회원 관리 예제(4)~(5) - 스프링 입문 강의 / 인프런 (0) | 2022.08.03 |
---|---|
[Java] Stream(스트림) 정리 (0) | 2022.07.30 |
[스프링 부트] API - 스프링 입문 강의 / 인프런 (0) | 2022.07.27 |
[스프링 부트] 정적 컨텐츠, MVC와 템플릿 엔진 - 스프링 입문 강의 / 인프런 (0) | 2022.07.24 |
[스프링 부트] View 환경설정, 빌드 후 실행 - 스프링 입문 / 인프런 (0) | 2022.07.20 |