본문 바로가기
Java

Unit TestCode작성해보기(Feat공부해보기)

by titlejjk 2023. 8. 28.

매일 포스트맨으로만 테스트하고있는 제 자신이 부끄럽기도하고 스프링에 대한 이해도를 조금이나마 좀더 높이고 싶어 테스트코드 작성에 대해서 공부해보았습니다.

가장 기본적이면서도 여지껏 어려워서 못하겠다고 미룬부분을 이번 프로젝트에 힘을 빌어 한번 작성해보려고합니다.

 

이 글에서는 기존에 만들어둔 비지니스로직중에서 "회원가입"에 대한 테스트 코드만을 작성하면서 공부해보려고 합니다.

 

먼저 왜 테스크코드를 작성해야하는지는 개발자라면 누구나 잘 아실 부분인것 같습니다.

테스트 코드 작성이 왜 중요한지에 대해서 먼저 공부해 보았습니다.

 

굳이 검증을 위한 좋은 프로그램이 있는데(귀차니즘..) 왜 테스트 코드를 만들어야 할까라는 생각도 있고, 저처럼 어렵다 생각해서 미루는 사람도 있고.. 여러 귀동냥으로 테스트 코드의 중요성을 듣긴 했지만 이번 프로젝트를 진행해보면서 습관처럼 가지고 가야겠다 라고 생각이 들었습니다.

 

테스트 코드는 내가 만든 메서드의 기능과 동작을 테스트하는 데 사용한 코드인 건 기본인데, 이를 통해 결함을 찾아내고 수정하는 중요한 과정 중 하나라 생각됩니다.

내가 의도한 대로 만들었는지 이게 왜 돌아가는지 그리고 어떻게 돌아가는지는 알고 적용시켜야하지 않나라는 생각이 굉장히 많이 들었습니다.

 

테스트 코드의 중요성에 대해서 더 말해보자면 코드의 품질보장,

그리고 새로운 기능을 추가하거나 코드를 수정할 때, 기존의 기능이 올바르게 작동하는지 확인하기 위한 회귀 테스트

테스트 코드를 통해 어떤 기능이 어떻게 동작하는지 명시적으로 보여줄 수 있는 코드의 문서화

버그나 이슈가 발생했을 때 문제의 원인을 빠르게 파악,

그리고 처음에는 시간이 걸릴 수 있지만 전체적으로 보자면 개발 프로세스를 더 빠르고 효율적이게 할 수 있다는 장점이 있습니다.

 

이를 통해 프로젝트의 신뢰성, 유지보수, 코드의 이해, 팀과의 협업 그리고 CI/CD에 이롭다는 점이 있을 것 같습니다.

 

좀 찾아보다 알았는데 테스트 주도 개발 방법론(TDD Test-Driven Development)

테스트 코드를 먼저 작성하고 이를 통과하여 실제 코드를 작성하는 방법이 있다는 것도 알게 되었네요..

 

오늘은 단위 테스트(Unit Test)를 먼저 사용해보려고 합니다.

가장 일반적으로 사용되는 테스트 중 하나이고 말 그대로 개별적인 코드 단위의 메서드들이 그 메서드를 만든 사람의 의도대로 움직이는지 확인하고 이상이 있으면 수정하는 테스트입니다.

그 중 Junit을 사용해볼 것입니다.

 

 

많이 부족해도 너그러이 봐주시고 잘못된점이 있으면 지적해주시면 감사하겠습니다.

 

위에 설명드렸던 것 처럼 회원가입에 대한 테스트코드를 작성할 것 입니다.

먼저 정상적인 회원가입 요청이 들어왔을 때 DB에 사용자가 저장이 되는지

이미 존재하는 이메일로 회원가입을 시도했을 때 실패하는지 확인

반드시 들어가야하는 필드값이 제대로 들어갔는지 그리고 제대로 들어가지 않았으면 회원가입이 실패하는지에

대해 테스트해보려고 합니다.

 

자바에서는 친절하게도 여기서 테스트하라고 명시되어 있습니다.

저는 이번 Test에서 JUnit와 Mockito(오픈소스 테스트 프레임워크)를 사용할 것 입니다.(사실 이 외에 뭐가 더 있는지는 잘 모르겠습니다...)

보통 어떻게 Test전용 클래스를 구성해서 사용하는지 몰라 그냥 회원가입 기능만 따로 골라서 테스트를 진행해 보겠습니다.

소심하게 LoginTestImplTest라 지어보고..

package com.project;

import com.project.user.dao.PetMapper;
import com.project.user.dao.UserMapper;
import com.project.user.dto.SignUpRequest;
import com.project.user.dto.UserDto;
import com.project.user.service.AuthServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.util.Arrays;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

public class LoginTestImplTest {

    @InjectMocks
    private AuthServiceImpl authService;

    @Mock
    private UserMapper userMapper;

    @Mock
    private PetMapper petMapper;

    private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @BeforeEach
    public void setup(){
        MockitoAnnotations.openMocks(this);
    }

    @Test
    @DisplayName("회원가입이 정상동작 함")
    public void testRegisterUserSuccessfully(){

        //Given
        SignUpRequest signUpRequest = new SignUpRequest();
        signUpRequest.setUserEmail("test@ex.com");
        signUpRequest.setUserPassword("testPwd");
        signUpRequest.setPetTypeIds(Arrays.asList(1, 2));
        
        String encryptedPassword = passwordEncoder.encode(signUpRequest.getUserPassword());
        UserDto userDto = new UserDto();
        userDto.createUser(signUpRequest, encryptedPassword);
        
        //When
        authService.signUp(signUpRequest);
        
        //Then
        verify(userMapper, times(1)).insertUser(any(UserDto.class));
        verify(petMapper, times(2)).insertUserPet(any());
    }
}

라는 코드를 작성해 보았습니다.

예전에 김영한 강사님 강의에서 봤던 Given, When, Then도 한번 써봤는데 간만에 보니 또 까먹을 까봐 이 개념도 적어보려고합니다.

 

먼저

@InjectMocks
이 어노테이션은 테스트 대상 클래스에 붙여주며, 이 클래스의 인스턴스를 생성할 때 Mock객체를 자동으로 주입합니다.

쉽게 말해 그냥 실제로 테스트하고자하는 서비스로 AuthServiceImpl를 사용하겠다. 라는 뜻입니다.

@InjectMocks
    private AuthServiceImpl authService;

@Mock
이 어노테이션은 Mock객체를 생성할 때 사용합니다.

여기서는 UserMapper와 PetMapper를 Mock객체로 생성하여 주입해줍니다.

 @Mock
   private UserMapper userMapper;

   @Mock
   private PetMapper petMapper;

@BeforeEach는

JUnit5에서 테스트 메서드가 실행되기 전 먼저 실행되는 메서드 인데 여기서는 setup()메서드를 사용함으로써 Mock객체를 초기화 하는 용도로 사용했습니다.

@BeforeEach
    public void setup() {
        MockitoAnnotations.openMocks(this);
    }

그리고
@Test,@DisplayName
@Test어노테이션의 기능은 실제 테스트를 수행할 메서드에 지정해주며,
@DisplayName어노테이션은 테스트메서드의 목적이나 설명을 추가해주는 어노테이션입니다. 어떤 테스트를 하고 있는지 혹은 했는지 알 수 있는 어노테이션입니다.

@Test
    @DisplayName("회원가입이 정상동작 함")
    public void testRegisterUserSuccessfully() {

        //Given
        SignUpRequest signUpRequest = new SignUpRequest();
        signUpRequest.setUserEmail("test@ex.com");
        signUpRequest.setUserPassword("testPwd");
        signUpRequest.setPetTypeIds(Arrays.asList(1, 2));

        String encryptedPassword = passwordEncoder.encode(signUpRequest.getUserPassword());
        UserDto userDto = new UserDto();
        userDto.createUser(signUpRequest, encryptedPassword);

        //When
        authService.signUp(signUpRequest);

        //Then
        verify(userMapper, times(1)).insertUser(any(UserDto.class));
        verify(petMapper, times(2)).insertUserPet(any());
    }

그리고 Given-When-Then패턴을 사용했습니다.

사실 이것밖에 배운적이 없어서 다른 패턴은 잘 모르겠습니다만 테스트 케이스를 명확하게 구조화하는 방법입니다.

Given : 테스트의 전제 조건을 설정

//Given
        SignUpRequest signUpRequest = new SignUpRequest();
        signUpRequest.setUserEmail("test@ex.com");
        signUpRequest.setUserPassword("testPwd");
        signUpRequest.setPetTypeIds(Arrays.asList(1, 2));

        String encryptedPassword = passwordEncoder.encode(signUpRequest.getUserPassword());
        UserDto userDto = new UserDto();
        userDto.createUser(signUpRequest, encryptedPassword);

When : 실제로 테스트하고자 하는 기능 즉 회원가입 로직을 실행

//When
        authService.signUp(signUpRequest);

Then : 테스트의 예상 결과를 검증

//Then
        verify(userMapper, times(1)).insertUser(any(UserDto.class));
        verify(petMapper, times(2)).insertUserPet(any());

여기서는 verify()라는 메서드를 사용하여 Mock객체의 메서드가 예상대로 호출되었는지 확인하는 방법을 사용 했습니다.

 

이렇게 작성한 후에 

를 눌러 테스트를 진행해보면

어떤 테스트를 했는지 알 수 있는 @DisplayName과 성공적으로 테스트가 되었는지 확인할 수 있는 글을 볼 수 있습니다.

 

혹시 더 좋은 방법이나 잘못된점이 있으면 언제든지 알려주시면 감사하겠습니다.

 

'Java' 카테고리의 다른 글

JAVA request.getParameter()  (0) 2023.06.29
Java Optional Class  (0) 2023.06.26
JAVA Jstl  (0) 2023.06.19
JAVA try-cath-finally 🧏‍♂️try-with-resources  (0) 2023.06.14
JAVA Cookie  (0) 2023.06.13

댓글