Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

회원 서비스 개발 & Test 본문

Spring Boot

회원 서비스 개발 & Test

Seung__ 2021. 7. 22. 16:53

실제 서비스와 과련된 로직을 개발해보자.

 

1. MemberService class 생성


public class MemberService {

    private final MemberRepository memberRepository= new MemoryMemberRepository();

    /**
     * 회원 가입
     */
    public Long join(Member member){
        //같은 이름이 있는 중복 회원 x

        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();

    }

    private void validateDuplicateMember(Member member) {
        //컨, 알트, m =method extraction.
        //Optional<Member> result =  memberRepository.findByName(member.getName());
        //optional로 한 번 감싸면 아래와 같이 처ㅣㄹ해 줄 수 있음
        //optional 없으면 if로 조건 걸어야함.

        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                        throw new IllegalStateException("이미 존재하는 회원입니다.");
                    });
    }


    /**
     * 전체 회원 조회
     */
    public List<Member> findMembers(){

        return memberRepository.findAll();

    }

    public Optional<Member> findOne(Long memberId){
        return memberRepository.findById(memberId);
    }

}

 

2. Optional 사용


return시 null이 return될 수 있을 때 Optional을 사용하면 이점이 있다.

 

만약 정책이 중복된 이름을 허용하지 않는다고 해보자. 게임에서 닉네임을 형성하는 것과 동일한 상황이다.

 

입력된 Member를 가지고 repository에서 findByName을 실행한다.

 

이 때 return 값이 검색 결과가 있을 수도, 있을 수도 있다. 

  Optional<Member> findByName(String name);

그래서 findByName의 return 타입을 Optional<Member>로 설정했다.

 

서비스 로직에서 Optional을 활용하여 처리를 할 수 있다.

 

findByName으로 return된 값이 존재하면? 이름이 중복된 상활ㅇ으로 IllegalStateException을 발생 시킬 수 있다.

  memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                        throw new IllegalStateException("이미 존재하는 회원입니다.");
                    });

만약 Optional이 아니라면 if문을 사용하여 null일 때 아닐 때 구분하여 작성해야한다..

 

*팁! 코드를 작성하다가 특정 코드들을 묶어서 메서드로 빼고싶다면..?
  "Ctrl + Alt + m" --> Extracion method 실행됨. 

 

3. Test

 

* Test 코드 작성 시 참고사항

  • given, when, then 문법을 활용하자
  • 해당 문법을 따라 작성하면 test를 누가 보더라도 명확하게 파악이 가능!
  • 예외처리 flow도 굉장히 중요함!

1) 정상적으로 회원가입이 진행되는 경우

 @Test //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.getId()).isEqualTo(findMember.getId());

    }

 

2) 회원가입 시 중복 회원이 발생하는 경우

    //Test는 정상 flow도 좋지만 예외 flow가 갱장히 중요하다!
    @Test
    public void 중복_회원_예외(){
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        //when
        memberService.join(member1);
          //예외 터진걸 잡으려면 try - cath쓰면됨

            //member2 저장시 IllegalStateException 터져야함.
        try{
            memberService.join(member2);
            //만약 여기서 catch로 안넘어가면 fail상황임
            fail();
        }catch (IllegalStateException e){
            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        }

        //then
    }

예외처리가 정상적으로 진행되었다.

그런데.. 요거때문에 try,catch넣기 좀 그래..

 

2-1) assertThrows(expectedType,  logic)

public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable) {
		return AssertThrows.assertThrows(expectedType, executable);

아래와 같이 사용할 수 있따.

해당 logic을 실행 했을 때, 예상되는 Type은 IllegalStateException.class이다. 

assertThrows(IllegalStateException.class, ()->memberService.join(member2));
    @Test
    public void 중복_회원_예외(){
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        //when
        memberService.join(member1);

		//then
        assertThrows(IllegalStateException.class, ()->memberService.join(member2));


    }

message를 받아처 확인해 볼 수 도 있다.

    //Test는 정상 flow도 좋지만 예외 flow가 갱장히 중요하다!
    @Test
    public void 중복_회원_예외(){
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        //when
        memberService.join(member1);

		//then
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

    }

 

 

3) 한꺼번에 돌려보자.

 

 마찬가지로 각 Test가 끝난 후 DB값을 clear해줘야 한다. 필수!

 MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository();

    @AfterEach //각 method실행 끝날대마다 실행
    public void afterEach(){
        memoryMemberRepository.clearStore();
    }

 

4)현재 Test는 MemberService를 Test하는 코드이다.

 

MemberService class에서 MemoryMemberRepository의 instance를 생성하였고,

 

Test에서도 Repository의 기능을 사용하기 위해 instance를 또 생성했다.

둘은 다른 instacne이다.

 

현재 코드는 MemoryMemberRepository의 field를 static으로 해두어서 문제는 없다.

왜냐 static은 instance가 아라 class level에 붙기 때문.

 

만약 satic을 떼버리면 문제가 발생한다.  instance마다 서로 다른 DB가 생기는 꼴이된다.

 

그렇다고 불필요하게 MemoryMemberRepository 객체를 두 개 사용할 필요는 없다.

 

따라서 같은 intance를 사용할 수 있도록 수정해야 한다.

 

 

3.  Dependancy Injection(DI)


 

MemberService 클래스에서 사용하는 MemberRepository를 해당 클래스에서 인스턴스를 생성하는 것이 아니라,

 

constructor를 통해 외부에서 주입을 받자.

@Service
public class MemberService {

    private final MemberRepository memberRepository;

    //repository를 직접 생성하는 것이 아니라, 외부에서 넣어주도록 변경.
    //MemberService입장에서 외부에서 Repository를 주입받음 = Dependancy Injection(DI)
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

아마 memberRepository에 에러가 발생할 텐데, 이는 MemberRepository가 @Bean으로 등록되어 있지 않기 때문.

 

MemberRepository에 @Component annotation을 붙여주면

Spring Bean으로 등록되어 Spring에서 관리한다.

@Component
public interface MemberRepository {

 

그리고 각 Test가 독립적으로 실행될 수 있도록,

실행 전에 MemoryMemberRepository생성하고 MemberService에 주입하도록 하자.

class MemberServiceTest {

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach(){
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

MemberService 입장에서 보면 외부로부터 memberRepository를 주입 받는다. 

 

이를 DI(Dependancy Injection), 의존성 주입이라고 한다.

 

test 실행 결과는 모두 통과!

 

4. GitHub : 210722, repository, test #2


 

GitHub - bsh6463/SpringBootClass

Contribute to bsh6463/SpringBootClass development by creating an account on GitHub.

github.com

 

Comments