스프링부트 단위테스트 어떻게 작성해야하는지 감이 안잡혀요.. 코드 리뷰 부탁립니다.

24.05.07 18:14 작성 조회수 51

0

안녕하세요. 단위테스트라는 것을 처음 작성해보는데 어떻게 작성해야할지 감이 안잡히네요 ㅠㅠ
일단 꾸역꾸역 작성을 해봤는데 이렇게 하는 게 맞는지 의문입니다. 너무 구체적으로 작성한 것은 아닌 지 고칠 부분이 있다면 피드백 해주시면 정말 감사하겠습니다.

아래 코드는 아이디, 이메일 중복 체크 및 레디스에 저장 된 인증번호를 체크한 후 회원가입을 구현한 코드입니다.

@Service
@Transactional
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;
    private final PasswordEncoder passwordEncoder;
    private final RedisService redisService;

    @Override
    public Member signUp(MemberRequest memberRequest) {
        // 회원가입 조건 검증
        verifySignUpCondition(memberRequest);

        Member member = memberRequest.toEntity();
        member.setPassword(passwordEncoder.encode(memberRequest.getPassword()));
        member.setRole(Role.MEMBER);
        member.setSns(Sns.NONE);

        memberRepository.save(member);

        // 회원가입 성공 시 레디스에서 관련 데이터 삭제
        redisService.deleteKey(memberRequest.getEmail());

        return member;
    }


    private void verifySignUpCondition(MemberRequest memberRequest) {
        checkLoginIdDuplicate(memberRequest.getLoginId());
        checkEmailDuplicate(memberRequest.getEmail());
        redisService.verifyAuthNum(memberRequest.getEmail(), memberRequest.getAuthNum());
    }

    private void checkLoginIdDuplicate(String loginId) {
        Optional<Member> findMember = memberRepository.findByLoginId(loginId);

        if(findMember.isPresent()) {
            throw new BusinessException(ErrorCode.LOGIN_ID_DUPLICATE);
        }
    }

    private void checkEmailDuplicate(String email) {
        Optional<Member> findMember = memberRepository.findByEmail(email);

        if(findMember.isPresent()) {
            throw new BusinessException(ErrorCode.EMAIL_DUPLICATE);
        }
    }
}

아래는 단위 테스트 코드입니다.

@ExtendWith(MockitoExtension.class)
class MemberServiceImplTest {

    @Mock
    private MemberRepository memberRepository;

    @Mock
    private PasswordEncoder passwordEncoder;

    @Mock
    private RedisService redisService;

    @InjectMocks
    private MemberServiceImpl memberService;

    @BeforeEach
    void setUp() {
        reset(memberRepository, passwordEncoder, redisService);
    }

    @Test
    @DisplayName("회원 가입 성공")
    void signUp_Success() {
        // given
        MemberRequest memberRequest = createMemberRequest();

        String encodedPassword = "encodedPassword";

        given(memberRepository.findByLoginId(memberRequest.getLoginId())).willReturn(Optional.empty());
        given(memberRepository.findByEmail(memberRequest.getEmail())).willReturn(Optional.empty());
        given(passwordEncoder.encode(memberRequest.getPassword())).willReturn(encodedPassword);

        willDoNothing().given(redisService).verifyAuthNum(memberRequest.getEmail(), memberRequest.getAuthNum());
        willDoNothing().given(redisService).deleteKey(memberRequest.getEmail());

        // when
        Member member = memberService.signUp(memberRequest);

        // then
        assertNotNull(member);
        assertEquals(member.getLoginId(), memberRequest.getLoginId());
        assertEquals(member.getEmail(), memberRequest.getEmail());
        assertEquals(member.getNickname(), memberRequest.getNickname());
        assertEquals(member.getPassword(), encodedPassword);
        assertEquals(member.getRole(), Role.MEMBER);
        assertEquals(member.getSns(), Sns.NONE);

        verify(memberRepository, times(1)).save(any(Member.class));
        verify(memberRepository, times(1)).findByLoginId(any());
        verify(memberRepository, times(1)).findByEmail(any());
        verify(passwordEncoder, times(1)).encode(any());
        verify(redisService, times(1)).verifyAuthNum(any(), any());
        verify(redisService, times(1)).deleteKey(any());
    }
}

답변 1

답변을 작성해보세요.

0

인프런 AI 인턴님의 프로필

인프런 AI 인턴

2024.05.08

안녕하세요, 인프런 AI 인턴입니다.

상언님, 단위 테스트 작성에 대해 질문 주셨군요. 제공해주신 코드를 보면, 단위 테스트의 기본 구조를 이해하고 잘 작성하셨습니다. Mockito를 사용하여 의존성을 가진 컴포넌트를 목 객체로 만들어 실제 환경과 분리된 상태에서 테스트를 진행한 점이 특히 좋습니다. 몇 가지 추가적인 제안을 드리자면:

  1. 가독성 향상: @DisplayName을 사용하여 각 테스트 케이스의 목적을 더 명확하게 기술하신 점이 좋습니다. 계속해서 모든 테스트 케이스에 대해 명확한 설명을 작성해 주세요. 이렇게 되면 테스트 코드를 읽는 다른 개발자들이 더 쉽게 이해할 수 있습니다.

  2. 예외 상황 테스트: 현재 성공 케이스에 대한 테스트가 잘 작성되어 있습니다. 추가적으로 회원가입 과정에서 발생할 수 있는 예외 상황들도 테스트해보는 것이 좋습니다. 예를 들어, 중복된 로그인 ID나 이메일로 회원가입을 시도했을 때, 적절한 예외가 발생하는지 검증하는 테스트를 추가해보세요.

  3. Edge Case 검증: 회원가입 데이터(validation) 검증 과정에서 잘못된 입력값이 들어왔을 때의 상황도 테스트해보는 것이 좋습니다. 예를 들어, 비밀번호나 이메일 형식이 잘못된 경우의 처리를 확인할 수 있습니다.

  4. 성능 및 리소스 검증: 테스트에서는 단순히 기능적인 측면 뿐만 아니라 리소스 사용, 성능 등도 간단하게나마 검토할 필요가 있습니다. 예를 들어, @MockBean을 이용한 서비스나 리포지토리가 너무 많은 작업을 하지 않는지, 비효율적으로 리소스를 사용하지 않는지 확인해볼 수 있습니다.

  5. 통합 테스트 고려: 단위 테스트 외에도, Spring Boot의 @WebMvcTest@SpringBootTest 등을 활용한 통합 테스트를 작성하는 것도 고려해보세요. 이를 통해 실제 애플리케이션 컨텍스트에서의 상호작용도 검증할 수 있습니다.

제공해주신 정보에서는 @WebMvcTest 등의 사용 예를 확인할 수 있는데, 이는 컨트롤러 레이어의 테스트에 매우 유용합니다. 회원가입 기능이 웹 어플리케이션의 일부라면, 해당 애노테이션을 사용한 컨트롤러 테스트도 고려해볼 만합니다.

단위 테스트는 애플리케이션의 견고성을 높이고, 리팩토링이나 기능 추가 시 안정성을 보장하는 데 중요한 역할을 합니다. 처음이라 어려움을 느낄 수 있지만, 지속적으로 다양한 케이스에 대해 테스트 코드를 작성해보면 능숙해질 것입니다.

도움이 될 만한 자료를 공유해드립니다.

Spring Boot 참조 문서 - 스프링 부트 애플리케이션 테스트

Spring Boot 참조 문서 - 스프링 시큐리티와 함께 테스트

화이팅입니다!