테스트 케이스를 작성할 때는 test 폴더 하위에 파일을 생성한다. 현재 내가 테스트할 파일이 repository > MemoryMemberRepository
파일인 것을 고려해서 test 폴더 하위에서도 같은 구조로 만들어주고, 자바 관례 상 파일명은 기존 파일명 뒤에 "Test"를 붙인 형태로 한다.
📁 test > ... > repository > MemoryMemberRepositoryTest.java
MemberRepository repository = new MemoryMemberRepository();
먼저 인터페이스인 MemberRepository
타입의 참조 변수로 구현 클래스인 MemoryMemberRepository
객체를 참조하도록 한다. 이렇게 하는 이유는 추후에 다른 구현체를 만들어 교체할 때 용이하게 하기 위해서이다. 또한 스프링에서는 보통 인터페이스를 기준으로 객체를 주입하기 때문에, 항상 인터페이스 타입으로 선언해주는 게 좋다.
그리고 메서드에 관한 테스트 케이스를 작성한다.
모든 테스트 케이스에는 @Test를 적어줘야 한다. 이를 위해 org.junit.jupiter.api의 Test를 import한다.
@Test
public void save() {
Member member = new Member();
member.setName("spring");
repository.save(member);
// Optional 객체에서 get을 통해 값을 읽어올 수 있다.
Member result = repository.findById(member.getId()).get();
// Assertions.assertThat(member).isEqualTo(result); -> 여기 Assertions에서 option+enter 후 static import
assertThat(member).isEqualTo(result); // 특별히 출력되는 것은 없지만 코드가 잘 돌아감 -> 두 객체가 같다는 뜻
}
Member 객체를 만들고 name 필드를 설정해준 후 repository
객체의 save 메서드를 실행시키면, id를 지정하고 store에 객체를 저장한 후 해당 객체를 반환한다. ➡️ [TIL] 250610_Spring: 회원 관리 예제 - 백엔드 개발(1) 내 📁 repository > MemoryMemberRepository.java 파일 코드 참고.
그 후 repository
객체의 findById 메서드를 실행시키면 Optional 객체를 반환하므로 get 메서드를 통해 객체를 꺼내 result라는 새로운 객체에 저장한다.
테스트를 위해 객체의 상태나 결과를 검증하기 쉽게 도와주는 라이브러리인 AssertJ를 import한다. 이를 static import할 경우 assertThat 함수를 바로 사용할 수 있다. assertThat(객체1).isEqualTo(객체2);
는 객체1과 객체2가 같은 코드인지 확인할 수 있도록 한다. 특별히 출력되는 것은 없지만, 두 객체가 동일할 경우 테스트를 실행했을 때 에러 없이 정상 동작한다.
@Test
public void findByName() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
// get()을 안하면 Optional<Member>, 하면 Member형임.
Member result = repository.findByName("spring1").get();
assertThat(result).isEqualTo(member1);
}
다른 메서드들도 이와 같은 방식으로 테스트 케이스를 작성한다. 객체를 생성해준 후 구현한 메서드를 사용해 반환 받은 객체와 동일한 객체인지 확인하는 방식으로 작성한다.
이렇게 여러 개의 메서드에 대해 테스트 케이스를 작성하다 보면, 테스트 전체를 실행시켰을 때 서로 엉켜서 에러가 날 수 있다. 이를 위해 @AfterEach을 활용해 테스트 하나가 끝날 때마다 리포지토리를 깔끔하게 비워주는 코드를 작성해보자.
@AfterEach
public void afterEach() {
// 테스트 하나가 끝날 때마다 저장소를 비움
repository.clearStore();
}
📁 repository > MemoryMemberRepository.java 파일 내에 clearStore
라는 이름의 store를 비우는 함수를 구현한 후, 테스트 파일에 위와 같은 함수를 추가한다. 이렇게 되면 테스트 전체를 실행시켰을 때 엉켜서 에러가 나는 일이 해결된다‼️
마지막으로 서비스를 개발하기 위해 Service 패키지를 만들어준다.
데이터 저장소와의 교류를 위한 메서드들이 담긴 리포지토리와는 달리, 서비스는 좀 더 비즈니스 로직에서 사용할 만한 용어들을 사용해서 구성하는 것이 좋다!
private final MemberRepository memberRepository = new MemoryMemberRepository();
위에서 설명했던 것처럼 인터페이스인 MemberRepository
타입의 참조 변수로 구현 클래스인 MemoryMemberRepository
객체를 참조하도록 생성한다. 이때 memberRepository
에 다른 객체를 재할당할 수 없도록 final을 통해 고정한다.
public Long join(Member member) {
validateDuplicateMember(member); // 이름 중복 여부 검사
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
// 요구사항 - 이름이 중복되면 안된다.
// Optional이기 때문에 ifPresent 사용 가능.
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
서비스의 필수 기능인 회원 가입을 구현한 부분이다. 요구사항 중 "회원의 이름이 중복되면 안된다."를 적용하기 위해 이를 체크하는 부분을 validateDuplicateMember
라는 이름의 함수로 추출하였다.
memberRepository 변수가 가지고 있는 findByName 메서드를 통해 인수로 들어온 member 객체의 name 값과 같은 name을 가지고 있는 회원이 있는지 검사한 후 이를 Optional로 감싸 반환한다.
Optional로 반환 받았기 때문에 ifPresent 메서드를 사용할 수 있고, 이를 통해 이름이 중복되는 회원이 존재할 경우 에러를 발생시킬 수 있다.
에러가 발생하지 않을 경우 정상적으로 member 객체를 store에 저장하고, 회원의 id를 반환한 후 함수를 종료한다.
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
서비스 내 회원 조회는 리포지토리가 가지고 있는 메서드들을 활용해 구현해주기만 하면 된다. so 간단!