- Service : 핵심 비즈니스 로직 구현
(중간 부분으로서 실제 중요한 동작이 많이 일어납니다.)
(주로 Repository에 구현된 메소드를 이용하여 구현❕)예시 코드 :
public class MemberService { private final MemberRepository memberRepository = new MemoryMemberRepository(); // 회원가입 public Long join(Member member){ validateDuplicateMember(member); // 중복 회원 검증 memberRepository.save(member); return member.getId(); } private void validateDuplicateMember(Member member) { // 중복 회원 검증 메소드 // 같은 이름이 있는 경우 회원 X Optional<Member> result = memberRepository.findByName(member.getName()); // Optional을 사용함으로써 Null값을 처리할 수 있을 뿐만 아니라 Optional의 다양한 메소드를 사용할 수 있다. result.ifPresent(m ->{ // ifPresent -> result에 null이 아닌 값이 있으면 동작 (Optional 일 때 사용 가능) throw new IllegalStateException("이미 존재하는 회원입니다."); }); } // 전체 회원 조회 public List<Member> findMembers() { return memberRepository.findAll(); } // 회원 한 명 조회 public Optional<Member> findOne(Long memberId) { return memberRepository.findById(memberId); } }
테스트를 실행하고자 하는 class에서 단축키 Ctrl + Shift + T 를 이용하여 자동으로 테스트 코드의 뼈대를 만들 수 있습니다.
단축키 실행 :
단축키 실행 결과 :class MemberServiceTest { @Test void join() { } @Test void findMembers() { } @Test void findOne() { } }
이렇게 단축키를 사용하면 별도의 Package를 만들고 내부에 Test용 class를 선언할 필요 없이 함수의 뼈대까지 빠르게 작성해주어 시간을 단축할 수 있습니다.
MemberServiceTest
class MemberServiceTest { MemberService memberService = new MemberService(); MemoryMemberRepository memberRepository = new MemoryMemberRepository(); @AfterEach public void afterEach() { memberRepository.clearStore(); } @Test public void 회원가입() throws Exception { //Given Member member = new Member(); member.setName("hello"); //When Long saveId = memberService.join(member); //Then Member findMember = memberRepository.findById(saveId).get(); assertEquals(member.getName(), findMember.getName()); } @Test public void 중복_회원_예외() throws Exception { //Given Member member1 = new Member(); member1.setName("spring"); Member member2 = new Member(); member2.setName("spring"); //When memberService.join(member1); IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));//예외가 발생해야 한다. assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); // 메세지가 동일한지 확인, 동일하다면 예외처리가 된 것 } }
위의 코드는 정상 동작하지만 한 가지 문제점이 있습니다.
바로 Test에 사용되는 memberService와 memberRepository가 각각 new를 사용해 직접 생성되고 있는 것입니다.
이는 memberService와 memberRepository가 서로 다른 인스턴스(Instance) 임을 의미하고 이는 Test에 사용되는 값들이 달라질 수 있음을 야기하기 때문입니다.따라서 사용하는 DB가 같아지도록 같은 인스턴스(Instance)로 만들어 주어야 합니다.
스프링 IOC 컨테이너 핵심 개념 중 하나인 DI는 의존성 주입이란 말로도 쓰입니다.
객체 간의 의존 관계를 외부의 조립기가 관리하여 불필요한 의존 관계를 없애거나 줄일 수 있으며 단위테스트 수행이 수월하다는 장점이 있습니다.
DI(의존성 주입) : 객체를 직접 생성하는게 아닌 객체를 외부에서 생성해서 주입시켜주는 방식
기존 MemberService의 객체 생성private final MemberRepository memberRepository = new MemoryMemberRepository();
DI를 적용한 MemberService의 객체 생성
private final MemberRepository memberRepository; // Repository를 직접 new로 생성하는 것이 아닌 외부에서 넣어주도록 함으로써 Test시 같은 객체 메모리를 사용할 수 있도록 함. (= DI 가능) public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; }
MemberServiceTest 객체 생성 부분 수정
MemberService memberService; MemoryMemberRepository memberRepository; @BeforeEach // 각각의 메소드가 시작할 때마다 동작한다. public void beforeEach() { memberRepository = new MemoryMemberRepository(); memberService = new MemberService(memberRepository); }
Test들은 독립적으로 시행되어야 하기 때문에 BeforeEach 어노테이션을 사용하여 각각의 메소드들이 실행될 때마다 객체를 생성하도록 합니다.
Repository 객체를 먼저 생성한 후 Service에 해당 객체를 넣어줌으로써 같은 객체 메모리를 사용할 수 있도록 합니다.
이 글은 인프런 "김영한" 님의 [스프링 입문] 강의를 듣고 개인적으로 학습하기 위해 정리한 글입니다. 🙂