목차
0. 환경
- m1 macbook
- IntelliJ IDEA(m1) - 202102
- java 11(AdoptOpenJDK-11.0.11)
- 자바를 설치하지 않았다면 아래의 링크를 활용해주세요.
1. 회원 서비스 개발
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import java.util.List;
import java.util.Optional;
public class MemberService {
//private final MemberRepository memberRepository = new MemoryMemberRepository(); //DI가 가능하도록 변경
private final MemberRepository memberRepository;
//DI
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
/**
* 회원가입
*/
public Long join(Member member) {
validateDuplicateMember(member); //중복 회원 검증
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
}); //line33
}
/**
*전체 회원 조회
*/
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}
- ifPresent(): Optional 클래스의 메소드로 데이터가 존재 할 경우 실행 (code line33)
- null check, null을 확인하는 if 문을 줄일 수 있습니다.
- 과거에는 ifnull을 사용했지만 지금은 Optional 클래스를 이용해 NULL을 처리합니다.
- Optional 클래스의 사용법은 Optional<Memeber> 이런 식으로 NULL이 의심되는 객체를 감싸주면 됩니다.
- Optional 클래스로 감싸진 데이터를 꺼내고 싶으면 get(), orElseGet()를 사용하면 됩니다. 그러나 직접 꺼내는 것을 권장하지는 않습니다.
- 메소드 네이밍 권장
- 리포지토리: save, findByName, findById처럼 저장소(DB)에 데이터를 넣고 빼고 하는 것처럼 네이밍을 권장합니다.
- 서비스: 비즈니스적 단어를 선택(좀 더 개발자스러운 단어)해서 메소드 네이밍을 권장합니다.
- 단축키(인텔리제이 + mac 기준)
- [command] + [option] + [v] = 자동 리턴 값 생성
- EX) memberRepository.findByName(member.getName());
- Optional<Member> byName = memberRepository.findByName(member.getName());
- [control] + [T] = 리팩토리 관련 단축키
- [command] + [option] + [m] = 특정 소스 다른 메소드로 분리(Extract Method)
- [command] + [option] + [v] = 자동 리턴 값 생성
2. 회원 서비스 테스트 코드 작성
[권장하는 테스트 케이스 스타일 구조]
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
public class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach //line17
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
@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));//예외가 발생해야 한다. line55
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
}
- @BeforeEach (code line17)
- 각 테스트 실행 전에 호출됩니다. 테스트가 서로 영향이 없도록 항상 새로운 객체를 생성하고, 의존 관계도 새로 맺어줍니다.
- assertThrows(): 메소드는 첫 번째 인자로 발생할 예외 클래스의 Class 타입을 받습니다. executable을 실행하여 예외가 발생할 경우 classType과 발생된 Exception이 같은 타입인지 체크합니다. (code line55)
- org.junit.jupiter.api.Assertions
- try catch 문을 사용하지 않아도 됩니다.
-
try { memberService.join(member2); fail(); //fail()를 사용하는 이유는 memberService.join(member2);가 성공을 하면 안되기 때문입니다. }catch (IllegalStateException e) { assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); }
- 단축키(인텔리제이 + mac 기준)
- [control] + [r] = 이전에 실행했던 것을 실행
- [command] + [shift] + [t] = 테스트 케이스 틀 자동생성
- 테스트가 필요한 클래스를 선택하고 단축키를 사용합니다.
- 테스트 경로에 같은 패키지 형태로 자동으로 테스트를 할 클래스의 테스트 케이스 소스를 생성해줍니다.
3. DI(의존성 주입)
- Dependency Injection
- 의존성 주입(dependency injection)은 하나의 객체가 다른 객체의 의존성을 제공하는 테크닉
- 의존 관계인 객체를 직접 생성하지 않고 다른 객체에서 의존 객체를 생성해서 넣어주는 형태
- 의존성 주입의 의도는 객체의 생성과 사용의 관심을 분리하는 것이다. 이는 가독성과 코드 재사용을 높여줍니다.
3.1. 예시(의존성 주입 전)
- 위의 최종 소스 전의 소스(MemberService, MemberServiceTest, MemoryMemberRepository)를 확인을 통해 의존성 주입에 대해 확인합니다.
<리포지토리 소스>
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
}
<초기 서비스 소스>
public class MemberService {
//의존 객체 직접 생성
private final MemberRepository memberRepository = new MemoryMemberRepository();
}
<초기 테스트 소스>
public class MemberServiceTest {
MemberService memberService = new MemberService();
MemberRepository memberRepository = new MemoryMemberRepository();
}
- 테스트(MemberServiceTest)를 실행하게 되면 서비스(MemberService), 리포지토리(MemoryMemberRepository) 객체를 생성하게 됩니다.
- 이 경우 서비스(서비스(MemberService) 객체를 생성하게 되면서 서로 다른 리포지토리(MemoryMemberRepository) 객체가 두 개가 생성되게 됩니다.
- 리포지토리(MemoryMemberRepository)의 저장소가 static 이기에 클래스 레벨이다 보니 서로 다른 객체를 이용해도 데이터에 문제는 없지만 만약 인스턴스급이라면 데이터의 내용이 달라지거나 할 수 있습니다.
3.2. 예시(의존성 주입 후)
<변경한 서비스 소스>
public class MemberService {
//의존 객체 직접 생성
//private final MemberRepository memberRepository = new MemoryMemberRepository();
private final MemberRepository memberRepository;
//생성자를 이용해 다른 객체를 통해 의존 객체를 받게 됩니다.
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
<변경한 테스트 소스>
public class MemberServiceTest {
//MemberService memberService = new MemberService();
//MemberRepository memberRepository = new MemoryMemberRepository();
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository(); //의존성 객체 생성
memberService = new MemberService(memberRepository); //의존성 주입(DI)
}
}
'Backend > 코드로 배우는 스프링 부트' 카테고리의 다른 글
[코드로 배우는 스프링 부트] 5. 스프링 빈과 의존관계 (자바 코드로 직접 스프링 빈 등록하기) (0) | 2021.11.01 |
---|---|
[코드로 배우는 스프링 부트] 4. 스프링 빈과 의존관계 (컴포넌트 스캔과 자동 의존관계 설정) (0) | 2021.10.26 |
[코드로 배우는 스프링 부트] 3-1. 회원 관리 예제 (도메인, 리포지토리) (0) | 2021.10.24 |
[코드로 배우는 스프링 부트] 2. Spring 웹 개발 기초(정적 컨텐츠, MVC, API) (0) | 2021.10.17 |
[코드로 배우는 스프링 부트] 1. SpringBoot 프로젝트 생성 (0) | 2021.10.11 |