비즈니스 요구사항 정리 및 설계
데이터 : 회원ID, 이름
기능 : 회원 등록, 조회
백엔드 계층 구조
컨트롤러 : MVC 컨트롤러 역할
서비스 : 도메인 객체를 이용한 핵심 비즈니스 로직이 구현됨
도메인 : 회원, 주문, 쿠폰 등과 같이 데이터베이스에 저장되고 관리되는 비즈니스 도메인 객체
리포지토리 : 데이터베이스에 접근하여 도메인 객체를 DB에 저장하고 관리하는곳
아래 프로그램에서는 DB없이 메모리를 저장공간으로 사용한다.
도메인
데이터인 회원 ID와 name이 들어간 Member객체이다.
회원 도메인과 리포지토리 만들기
MemberRepository interface이다.
리포지토리는 도메인 객체를 DB에 저장하고 관리하는곳이기 때문에 domain.Member를 import한다.
이후 Member들을 DB에 저장하거나 관리하는 메소드들을 선언한다.
DB에 따라 해당 인터페이스를 상속받아 구현한다.
Memory를 DB로 구현한 MemoryMemberRepository
MemberRepository를 상속받아 MemoryMemberRepository를 구현한다.
해당 인터페이스의 save, findById, findByName, findAll 메소드들을 해당 클래스에서 구현한다.
1씩 sequence를 더하면서 id값을 부여, store(Map)라는 임시저장소(Memory DB)에 member 저장
findById : id값과 일치하는 id를 store에서 탐색
Null이 반환될 수 있기 때문에 Optional.ofNullable로 탐색
findByName : name값을 기반으로 store에 저장된 값 탐색
stream()의 경우 for문으로 모든원소에 접근하듯이 모든 원소들에대해 접근한다고 생각하면 된다.
filter를 통해 name과 일치하는 멤버들을 걸러내고, findAny()를 통해 걸러진 값을 반환한다.
즉 store에 저장된 값중 이름이 name과 일치하는 경우를 반환한다.
모든 값들을 Member List로 반환한다.
회원 리포지토리 Test코드 작성
개발한 기능들을 main메서드를 실행시켜 테스트하는 경우 시간이 오래걸리고, 한번에 모두 테스트하기 어렵다는 단점이 있기 때문에 테스트코드를 생성하여 메소드들을 테스트하는것이 좋다.
test내에 리포지토리 패키지를 생성하여 test클래스를 작성해준다.
@Test 어노테이션을 사용하여 해당 메소드가 테스트 메소드임을 명시해준다.
위처럼 해당 테스트 메소드를 구현한뒤 해당 메소드만 실행시켜 테스트가 가능하다.
save를 실행시킬경우 해당 테스트코드 내에서 오류가 발생하거나 실패를 하게되면 해당 실행에서 경고를 반환한다.
테스트코드가 성공할경우 아래처럼 정상 종료가 된다.
테스트코드는 항상 해당 코드를 실행시키고 검증까지 완료해야한다. save 메소드의 경우 Member 데이터 생성과 save함수 실행, 이후 findById를 통해 해당 id를 검색하고 assertThat(member).isEqualTo(result)를 통해 검증을 완료했다.
findByName 테스트 코드
findByName을 검증하기 위해 데이터를 만들고 findByName을 통해 result를 반환받고 assertThat(result).isEqualTo(member1)을 통해 검증했다.
findAll() 테스트 코드
2개의 member를 생성 및 save하고 findAll()을 실행한다.
이후 findAll()의 반환의 결과가 2개 인지 확인하여 검증한다.
테스트코드의 경우 모든 메소드들을 한꺼번에 실행하여 테스트하는것도 가능하다.
여러 메소드를 한꺼번에 실행할 경우 각 메소드들이 순서와 상관없이 실행된다.
각 메소드들이 실행되면서 같은 데이터를 save하는 경우 에러가 발생한다.(중복 저장 예외)
이러한 문제를 해결하기 위해 아래의 코드를 추가해준다.
@AfterEach는 해당 클래스내의 각 메소드들이 실행을 끝낼때 마다 실행되게하는 어노테이션이다.
MemoryMemberRepository의 clearStore메소드
위 메소드를 통해 각 메소드들이 끝날 때마다 저장소를 비워준다.
-> 중복 저장 예외를 방지할 수 있다.
위의 개발과정은 MemoryMemberRepository를 모두 구현하고, 테스트 코드를 통해 해당 클래스의 메소드들을 검증 했다. 해당 개발 과정을 뒤집어 테스트 코드를 먼저 작성하고 검증이 완료된 후 메소드를 구현할 경우 테스트 주도개발(TDD)이라고 한다.
MemberService 메소드 작성
Join() 메소드
join 내부에서 member중복을 검사하는 코드를 작성한 뒤 외부의 새로운 메소드로 생성했다.
ifPresent를 통해 findByName의 반환값이 존재한다면 throw new IllegalStateExceptioon("이미 존재하는 회원입니다.")를 실행시킨다.
ifPresent는 값이 존재할 경우 내부 로직을 실행시키는 기능을 제공한다.
위와 같이 MemberService 코드를 구현했다.
회원가입, 전체 회원조회, 회원ID를 통한 회원 조회 등 비즈니스적인 메소드들이 구현된다.
MemberService 테스트 코드 구현
위처럼 테스트코드의 메소드명을 한글로 지정해도 상관없다.
테스트 코드를 구현할 때 given-when-then 구성으로 코드를 작성하는것이 좋다.
given : 주어지는 것
when : 실행했을 때
then : 결과
어떤 것이 주어진 상황에서 코드를 실행했을 때 결과가 이렇게 나와야 한다는 과정을 정리하여 작성하는 방법이다.
given-when-then 의 회원가입 테스트코드
멤버가 given으로 주어지고 join메소드를 실행했을 때 검증을 위해 findOne을 통해 나오는 결과가 무엇인지 확인하는 과정이다.
중복 회원가입 예외 테스트코드
assertThrows는 두번째 인자로 주어진 로직 실행시 첫번째 인자에 해당하는 예외가 발생하는지 검사하는 함수입니다.
첫번째 인자가 IllegalStateException이 아닌 NullPointerException이라면 assertThrows에서 에러를 반환한다.
해당 로직에서 정상적으로 IllegalStateException 을 발생시키면 해당 예외를 예외객체 e에 저장하고,
assertThat을 통해 해당 예외 메세지를 "이미 존재하는 회원입니다." 와 비교하여 검증을 완료한다.
Service테스트 코드에서도 테스트별로 멤버들을 생성하고 저장하기 때문에 아래 코드를 넣어준다.
MemberService의 생성자 코드
MemberService의 생성자 인자로 memberRepository가 주어지며 각 MemberService마다 memberRepository를 별도로 갖게 된다.
테스트코드에서의 MemberService
@BeforEach를 통해 모든 메소드들이 실행되기전 해당 코드가 실행되도록 했다.
테스트코드 메소드들이 실행될 때 마다 MemoryMemberRepository()를 생성하고 서비스를 따로 생성해주며 서비스 객체 생성때마다 memberRepository를 생성해준다.
즉 각 테스트 메소드들이 각각의 memberService와 memberRepository를 갖게된다.
'Programming > Spring' 카테고리의 다른 글
웹 MVC 개발 (0) | 2022.01.08 |
---|---|
스프링 빈과 의존관계 (0) | 2022.01.06 |
웹개발 기초 (0) | 2021.12.21 |