일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 컴포넌트스캔
- Nodejs
- AWS
- Spring
- nestjs auth
- spring boot
- git
- TypeORM
- 가상면접사례로배우는대규모시스템설계기초
- 코딩테스트
- C++
- 시스템호출
- thymeleaf
- 파이썬
- 구조체배열
- python
- @Component
- OpenCV
- @Autowired
- nestJS
- 코테
- 카카오
- 해시
- 알고리즘
- 카카오 알고리즘
- C언어
- 프로그래머스
- 스프링
- 카카오 코테
- nestjs typeorm
- Today
- Total
공부 기록장 💻
[Spring] 비즈니스 요구사항 정리와 회원 Domain, Repository 생성 및 Test Case 작성 (회원 관리 예제 백엔드 개발) 본문
[Spring] 비즈니스 요구사항 정리와 회원 Domain, Repository 생성 및 Test Case 작성 (회원 관리 예제 백엔드 개발)
dream_for 2023. 1. 9. 11:28인프런 "스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술" 정리
회원을 등록하고 조회하는 간단한 회원 관리 예제 백엔드 서버를 개발해보자.
단, 아직 데이터 저장소 (DB) 가 선정되지 않음을 고려한 가상의 시나리오를 배경으로 한 예제이다.
웹 어플리케이션의 기본 구조
우선 기본적인 웹 어플리케이션의 구조는 다음과 같이 도메인, 컨트롤러, 서비스, 리포지토리로 구성된다.
- Domain: 비즈니스 도메인 객체 (회원, 주문, 쿠폰과 같이 주로 DB에서 저장하고 관리하는 대상 객체)
- Controller : 웹 MVC의 컨트롤러, API를 만드는 경우 컨트롤러의 역할
- Service : 핵심 비즈니스 로직 구현 (회원의 중복 가입 방지 로직 등)
- Repository: DB에 접근하여 도메인 객체를 DB에 저장하고 관리하는 역할
클래스의 의존 관계 설정
회원 비즈니스 로직에는 MemberService가 있고, 회원 정보를 저장하는 MemberRepository의 경우는 interface로 설계를 하자. (JPA, MyBatusm RDB NoSQL 등 구체적인 데이터 저장소가 아직 선정되지 않은 시나리오이기 때문에 인터페이스를 만들고, 추후 구현 클래스로 변경)
그리고 개발을 진행하기 위해 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소를 사용하도록 하자. 따라서 우선 MemberRepository의 구현체로는 MemoryMemberRepository를 만들자. (memory로 단순하게 저장하는 방식)
회원 도메인과 리포지토리 만들기
우선 다음과 같이 hellospring 패키지 아래에 domain이라는 패키지를 만들어 Member 클래스 (도메인)을 생성하자.
Member 클래스
다음과 같이 id, name 변수, 그리고 각각에 맞는 getter, setter 을 작성해 주었다.
MemberRepository 인터페이스
다음으로는 회원 정보를 DB에 접근하여 저장하고 관리하는 MemberRepository 인터페이스를 만들자.
hellospring 내에 repository 패키지를 만들고, 내부에 MemberRepository 인터페이스를 작성하자.
아래와 같이 회원 정보를 저장하는 save() 메서드,
id 값으로 회원을 찾는 findById(),
name 값으로 회원을 찾는 findById(),
그리고 모든 회원 정보를 반환하는 findAll() 메서드의 틀을 작성해주자.
MemoryMemberRepository 구현체 클래스 (MemberRepository 인터페이스)
이제 MemberRepository 인터페이스의 구현체인 MemoryMemberRepository를 만들어보자.
아래와 같이 implements 키워드를 이용해 인터페이스 구현을 알리면, MemberRepository 인터페이스에서 작성한 함수들 중 어느 것을 구현할지 선택하는 화면이 나타난다.
모든 메서드를 선택 후, static 클래스 store과 static 변수 sequence를 작성해주도록 하자.
id와 Member 을 HashMap 형태로 저장할 store 객체, 그리고 0부터 차레대로 id 값을 증가시킬 long형 sequence를 작성하자.
다음으로 각 메서드를 구체화해보자.
save() 메서드
member 객체의 setId() 메서드를 이용해 sequence 변수를 증가시킨 값으로 id 값을 설정한 후, store 맵에 member의 Id와 member 인스턴스를 저장한다.
findById() 메서드
store.get(id) 를 단순히 반환하는 것보다는, id 값에 해당하는 인스턴스를 발견하지 못하는 경우 null 을 반환할 가능성이 있기 때문에 Optional 클래스로 한 번 감싸서 리턴하도록 한다.
(Optional는 Java8부터 등장한 클래스로, ofNullable() 은 값이 null일 가능성이 있음을 명시적으로 나타내는 메서드이다. (참고))
findByName() 메서드
store map values의 loop를 끝까지 돌면서 member의 name이 인자로 받은 name 값과 동일한 경우 해당 member을 리턴하고, 아닌 경우에는 null 값을 반환하게 된다.
findAll() 메서드
store 맵의 모든 값들을 ArrayList 컬렉션 형식으로 반환하게 된다.
회원 Repository 테스트 케이스 작성
이제 개발한 기능을 테스트해보자.
자바의 main 메서드를 실행하기보다, 자바의 JUnit 테스트 프레임워크를 이용해 테스트 케이스를 작성하고, 검증해보도록 하자.
우선 test 디렉터리의 java>hello>hellospring 아래에, repository 패키지를 만들고 MemoryMemberRepositoryTest 클래스를 생성하자.
save() 테스트
이후에 아래와 같이 MemoryMemberRepository 클래스 객체인 repository를 선언해준 후,
@Test 어노테이션과함께 회원 정보를 제대로 저장하는지 검증하는 save() 테스트 메서드를 작성해보자.
Member 객체 생성 후, "spring" 값으로 name 을 지정해준다.
이후에 repository에 member 을 저장한 후, result에 repository에 member의 Id값을 이용해 얻은 Member 객체를 result에 저장해준다.
결국 member와 result는 동일한 값이 되어야 정상적이다.
이를 검증하기 위해, junit의 클래스 Assertions의 assertEquals 메서드를 이용하여 result와 member의 값이 같은지 확인해보자.
save() 메서드를 실행하면, 검증이 정상적으로 완료되었다는 표시 Tests passed가 다음과 같이 나타난다.
만약 assertEquals() 메서드의 두번째 인자에 null 값을 지정하여 동일하지 않은 객체를 검증하는 경우,
아래와 같이 테스트가 실패했음을 나타내는 에러 코드가 표시된다.
이번엔 assertj의 core api인 Assertions의 assertThat() 메서드를 활용해보도록 하자.
findByName() 테스트
이번에는 name의 값을 이용해 인스턴스를 찾는 findByName() 의 테스트를 실행해보자.
"spring1"이라는 name 값을 갖는 member1과 "spring2"라는 name 값을 갖는 member2 인스턴스를 만들어 repository에 저장한 후,
"spring1" 이라는 값으로 객체를 탐색한 후, 테스트 코드를 실행해보자.
findAll() 테스트
이제 회원 인스턴스 2개를 맵에 저장하고, 맵에 저장된 값이 2개인지를 검증하는 코드를 작성해보자.
findAll() 테스트 메서드만 실행한다면 위의 테스트 코드는 성공적이지만,
class level에서 지금까지 만든 3개의 메서드를 모두 실행한다면 findByName() 과 findAll()에서 동일한 name을 갖는 값이 map에 저장되므로 테스트에 오류가 있는 것으로 판단하게 된다.
따라서 각 테스트 메서드 실행 후 map 메모리를 초기화해주는 과정이 필요하다.
@AfterEach 사용
테스트 케이스의 각 메서드는 서로에 대한 의존 관계가 만들어져 서는 안된다.
따라서 테스트 케이스 수행 후, 어떤 공용 데이터는 전부 초기화되어야만 다음 케이스 수행이 원활해진다.
@AfterEach 어노테이션을 사용하여 각 테스트 메서드를 실행 한 후 repository의 store 맵을 초기화하는 코드를 작성해보자.
먼저 MemoryMemberRepository 클래스에 clearStore() 메서드를 작성하자.
store에 저장된 모든 값들을 초기화하게 된다.
다음으로 MemoryMemberRepositoryTest에 @AfterEach 어노테이션 작성과 함께 afterEach() 메서드를 만들자.
repository의 clearStore() 메서드를 실행하여 메모리를 모두 초기화하게 된다.