[나 혼자 스프링부트!] 2) Service 로직 단위 테스트 작성하기

JoonYoung Maeng·2021년 11월 8일
1
post-thumbnail

🥇 단위 테스트의 법칙 (F.I.R.S.T)

  1. Fast : 단위 테스트는 빠르게 진행되어야 한다. 서비스가 커지면서 단위 테스트의 실행도 오래 소요 되기 때문이다.
  2. Independent : 단위 테스트는 테스트 하고자 하는 모듈만 테스트 해야한다. 즉, 각각의 테스트는 실행 순서 등과 상관없이 독립적이어야 한다.
  3. Repeatable : 단위 테스트는 어떠한 환경에서도 반복적으로 작성한 테스트 코드가 실행 될 수 있어야 한다.
  4. Self-validating : 단위 테스트는 테스트를 실행함으로 써 로직에 대한 자체적인 검증이 가능하고, 성공 또는 실패를 판단할 수 있어야 한다.
  5. Timely : 단위 테스트는 실제 서비스 로직이 들어간 코드를 만들기 전에 단위 테스트 코드가 선행되어야 한다. 즉, 테스트 기반 개발 (TDD)로 진행해야 한다.

📄 @Service 로직 단위테스트 작성 예제

MemberRepository와 MemberService가 각각 있다고 가정하자.

//MemberService
package com.yourecipe.member.service;

import com.yourecipe.member.model.Member;
import com.yourecipe.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor    // 생성자 주입
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    @Override
    public boolean  signUpMember(Member member) {
        return memberRepository.joinMember(member) > 0;
    }
}
//MemberRepository
package com.yourecipe.member.repository;
import com.yourecipe.member.model.Member;
import org.springframework.stereotype.Repository;

@Repository
public interface MemberRepository {
    // 회원정보 등록하기
    int joinMember(Member member);

    // 회원정보 가져오기
    Member findMember(int memberId);

    // 회원정보 수정하기
    int updateMember(Member member);

    // 회원정보 삭제하기
    int quitMember(int memberId);
}

통합 테스트가 아닌 서비스 로직에 대해서만 단위 테스트를 하기 위해서는 Repository나 Controller에 모두 독립적이어야 한다.

따라서, 단위테스트를 작성할 때는 @Mock 애노테이션을 이용해서 서비스 코드에 의존성 주입을 해줘야 하는 빈 객체들을 가짜 객체로 만들어준다.

@Mock
private MemberRepository memberRepository;

그리고 실제로 주입받고자 하는 서비스 코드는 @InjectMocks 애노테이션을 이용해 Mock으로 만들어진 가짜 객체들을 주입해준다.

반드시 Service 구현체를 의존성 주입해줄 객체로 설정해야 한다. 만약 Service interface를 주입 대상으로 설정하면 테스트 오류가 발생한다.

@InjectMocks
private MemberServiceImpl memberService;

단위 테스트는 given, when, then 순서로 구성하는 것이 바람직하다.

Given(테스트 사전 조건이 주어짐), When(실제 테스트가 진행됨), Then(테스트 결과를 알려줌) 순서로 나는 간단하게 이해하고 작성중이다.

@Test
@DisplayName("회원 서비스 : 회원 가입 테스트(성공)")
void 회원가입_성공() {
    //given
    given(memberRepository.joinMember(any())).willReturn(1);
    Member member = createMemberForTest();

    //when
    boolean result = memberService.signUpMember(member);

    //then
    assertThat(result).isEqualTo(true);
}

// 테스트에 사용할 member 객체 생성
private Member createMemberForTest() {
    return Member.builder()
            .memberId(1)
            .email("zayson.maeng@gmail.com")
            .nickname("zayson")
            .profileImg("test.com")
            .build();
}

image

라인 별로 코드를 알아보면, given(Object MethodCall) 메소드는 `import static org.mockito.BDDMockito.given;` 해당 라이브러리에서 제공되는 메소드로 의존성 주입된 객체 , 즉 "대체하는 객체가 호출한 메소드가 주어진다면" 으로 해석하면 된다. 위 테스트 코드에서는 MemberService에 대한 코드를 테스트하는 것이지 MemberRepository를 테스트 하는 것이 아니기 때문에 Repository 객체 ( Mock 객체 )의 메소드를 호출하고, 호출하는 메소드의 객체도 서비스 로직 테스트에서는 의존하지 않으므로 any() 메소드로 작성해주었다.

마지막으로, willReturn() 메소드를 통해 Repository가 호출 후 어떤 값을 결과로 줄 지에 대해 작성한다.

when 부분에서는 실제 테스트할 서비스 로직을 호출한다. memberService의 signUpMember 메소드를 호출해 결과값을 저장한다. 실제 서비스 코드는 Repository의 joinMember 메소드를 호출 후 결과값이 양수라면 true를 던져주는 로직이다.

따라서, Then 부분에서 1값을 가져오고 true를 결과로 저장할 것이기 때문에 assertThat 메소드로 실제 결과와 예상 결과를 비교해주면 된다.

💡 테스트 코드를 작성할 때는 반드시 성공하는 테스트만 작성하는 것이 아닌 실패하는 테스트 코드도 작성하는 습관을 들이자.

🖇️ 참고

https://jiminidaddy.github.io/dev/2021/05/20/dev-spring-단위테스트-Repository/

profile
백엔드 개발자 지망생입니다!

0개의 댓글