1. 회원 관리 비지니스 요구사항 정리
- 비지니스 요구사항
- 데이터 : 회원ID, 회원이름 / 기능 : 회원 등록, 회원 조회
- 가정 : DB 선정X → 인터페이스로 구현하여 변경가능하도록 설계 - (일반적인) 웹 애플리케이션 계층 구조
- 컨트롤러 : 웹 MVC의 컨트롤러 역할
- 서비스 : 비즈니스 도메인 객체를 가지고 핵심 비즈니스 동작 로직 구현 ex) 회원 중복가입 방지 등등
- 도메인 : 비즈니스 도메인 객체 ex) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨
- 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
2. 회원 도메인과 리포지토리 만들기
- Optional 클래스
- Stream(스트림)
- clear() : 모든 데이터 삭제
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
// member에서 가지고 온 name과 인자로 넘어온 name이 같은 경우가 발생할 경우 반환
// null 값인 경우 null 값 반환
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore(){
store.clear(); // store의 모든 데이터 삭제
}
}
3. 회원 리포지토리 테스트 케이스 작성
- 테스트 케이스 방법
- X : 자바의 main 메서드를 통해 실행, 웹 어플리케이션의 컬트롤러를 통해서 해당 기능을 실행
→ 오래 걸림, 반복실행 어려움, 여러 테스트를 한번에 실행하기 어려움
- O : JUnit 프레임워크 - @AfterEach
- 각 테스트가 끝날 때마다 실행되는 콜백함수 - assertThat(실제값).isEqualTo(기댓값)
- 실제값과 기댓값 비교, 다르면 오류 발생
※ TDD (테스트 주도 개발)
: 검증 가능한 테스트 클래스 작성후 리포지토리 만드는 것, 테스트를 먼저 만들고 구현클래스를 만들어 돌려보는 것
: 우리가 한것은 tdd 아님 그냥 테스트 케이스 만들어 해본 거임
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
/* 모든 테스트 한번에 돌리면 오류 발생 => findByName()오류 */
// -> why?
// -> 테스트 순서는 랜덤이며, 메소드 별로 따로 따로 동작
// -> findAll()이 test 되어 지면서 만든 객체들을 findByName()에서 다시 사용되어 객체가 같지 않아 오류
// ==> [해결법] : 한 메소드의 test가 끝난 후에, 데이터를 clear를 해줘야 함 ==> @afterEach를 통해 데이터 지워주는 콜백함수 적용
@AfterEach // 한 메소드의 테스터 끝날 때마다 해당 메소드 실행되어 데이터를 지워주는 역할, 콜백함수
public void afterEach(){
repository.clearStore();
}
@Test
public void save(){
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get(); // 검증, 잘 되었는지 확인
// System.out.println("result = " + (result == member)); // 두 값 비교 (1)
// Assertions.assertEquals(member, result); // (기대값, 실제값) 두 값 비교 (2)
// 출력되는 값은 없지만 두 값이 다르면 오류발생
assertThat(result).isEqualTo(member); // 두 값 비교 (3) // 다르면 오류발생
}
@Test
public void findByName(){
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
Member result = repository.findByName("spring1").get();
assertThat(result).isEqualTo(member1);
}
@Test
public void findAll(){
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(2);
}
}
4. 회원 서비스 개발
- [IntelliJ] 메소드 자동 추출 : ctrl + alt+ m
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());
result.ifPresent(m -> { // null 값이 아닌 존재하는 값이 있을 경우
throw new IllegalStateException("이미 존재하는 회원입니다.");
});*/
// 위보다 이런 형태로 만들면 간단하게 완성!!
memberRepository.findByName(member.getName())
.ifPresent(m -> { // null 값이 아닌 존재하는 값이 있을 경우
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
/* 전체 회원 조회 */
public List<Member> findMembers(){
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId){
return memberRepository.findById(memberId);
}
}
5. 회원 서비스 테스트
- [IntelliJ] 해당 클래스의 자동 테스트케이스 만들기 : ctrl + shift + t
- test 시, given-when-then 형식으로 작성하기
- 의존성주입(DI)
- 필요한 객체를 직접 생성하는 것이 아닌 외부로 부터 필요한 객체를 받아서 사용하는 것
- 객체간의 결합도를 줄이고 코드의 재활용성을 높여준다. - assertThrows(예외.class, () -> 예외처리 구문);
- try-catch 같은 예외 처리 방법
class MemberServiceTest {
MemberService memberService;
// service에서의 리포지토리와 test에서의 리포지토리가 같아야 하는데 같을까? No
// ==> DI(의존성 주입) -> @BeforeEach 참고
// MemoryMemberRepository memberRepository = new MemoryMemberRepository();
MemoryMemberRepository memberRepository;
@BeforeEach // 각 테스트 전에 실행
public void beforeEach(){
/* 의존성주입(DI)
* : MemberService 클래스가 직접 리파지토리를 생성하지 않고 외부에서 넣어준주는 것*/
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
@AfterEach
void clear(){
memberRepository.clearStore();
}
@Test
void 회원가입() { // test는 이름 한글로 변경 가능
//given : 이런 상황이 주어졌을 때
Member member = new Member();
member.setName("hello");
//when : 이것을 실행했을 때
Long saveId = memberService.join(member);
//then : 결과가 이렇게 나와야 함
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
public void 중복_회원_예외(){
//given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
//when
memberService.join(member1);
// try-catch 같은 예외처리 간단하게 하는 방법
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
/*try{
memberService.join(member2);
fail("예외가 발생해야 합니다.");
} catch(IllegalStateException e){
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}*/
//then
}
[ 출처 ]
'Spring > 개념' 카테고리의 다른 글
[스프링입문] CH05. 회원 관리 예제 - 웹MVC 개발 (0) | 2021.08.10 |
---|---|
[스프링입문] CH04. 스프링 빈과 의존관계 (0) | 2021.08.10 |
[스프링입문] CH02. 스프링 웹 개발 기초 (0) | 2021.08.03 |
[스프링입문] CH00. JDK 설치 및 프로젝트 생성 (0) | 2021.07.08 |
[스프링입문] CH01. 프로젝트 환경설정 (0) | 2021.06.26 |
댓글