ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 3. 유닛 테스트를 어노테이션과 함께 만들 수 있다
    스프링개발자/202 - 디버깅+테스팅 2020. 8. 14. 10:53

    내 어플리케이션의 코드가 꽤 많은 상황에서, 몇가지 기능을 추가 구현했는데, 기존 코드가 작동이 안된다면?

    실제로 많이 일어나는 문제이다. 이전에 작동 하던 것은 여전히 작동하고, 새로 동작하는 것 또한 잘 작동하도록 하는것이 테스트의 역할이고 테스트가 중요한 이유이다. 가장 작은 테스트 단위인 유닛 테스트를 만들어보자.

     

    이전 글과 마찬가지로, 여기의 코드베이스를 사용한다; (203시리즈에서 만든 코드이다)

    https://github.com/2ndPrince/routeOptimization/pull/8

     

    1. DispatchService의 유닛 테스트를 만들자

    @Service
    public class DispatchService {
        public void validate(DispatchRequest request) {
    
            List<ValidationError> errorList = new ArrayList<>();
    
            request.getRides().stream()
                    .flatMap(r -> Stream.of(r.getPickup(), r.getDropOff()))
                    .collect(Collectors.toSet())
                    .forEach(location -> validateLocations(location, errorList));
    
            if(!errorList.isEmpty()){
                throw new ValidationException(errorList, request.getId());
            }
        }
    
        public void validateLocations(Location location, List<ValidationError> errors){
            Double longitude = location.getLongitude();
            Double latitude = location.getLatitude();
    
            boolean isLonValid = (-180d <= longitude) && ( longitude <= 180d);
            boolean isLatValid = (-90d <= latitude) && ( latitude <= 90d);
    
            if(!isLatValid) errors.add(new ValidationError(ValidationError.Type.BAD_RIDE, "Location", "Invalid Lat Location. Check your Lat", 1));
            if(!isLonValid) errors.add(new ValidationError(ValidationError.Type.BAD_RIDE, "Location", "Invalid Lon Location. Check your Lon", 1));
        }
    }

    본 서비스는 이렇게 생겼고, 우리는 validateLocations 메소드에 대한 유닛 테스트를 만들어보자. 테스트 클래스들은 보통 test directory의 같은 패키지명 아래 존재한다. DispatchService에서 인텔리제이 단축키 "Ctrl+Shift+T" 를 눌러 테스트 클래스 자동 생성. Junit5를 선택한다.

     

    DispatchServiceTest 클래스의 내용은 다음과 같다;

    package me.ndPrince.routeOptimization;
    
    import me.ndPrince.routeOptimization.model.ValidationError;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.mockito.InjectMocks;
    import org.mockito.junit.jupiter.MockitoExtension;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import static org.junit.Assert.assertEquals;
    
    @ExtendWith(MockitoExtension.class)
    class DispatchServiceTest {
    
        @InjectMocks
        private DispatchService dispatchService;
    
        @Test
        public void test_validateLocations(){
    
            List<ValidationError> errorList = new ArrayList<>();
            Location l1 = new Location(-501.2, 5.3);
            dispatchService.validateLocations(l1, errorList);
            assertEquals(1, errorList.size());
    
        }
    
    }

    어노테이션에 ctrl+click을 하여 나온 javadoc의 정보는 다음과 같다;

     

    1) @ExtendWith(MochkitoExtension.class) : mock을 initialize 해주고, stubbing을 담당하는 익스텐션이다. JUnit4의 MockitoJUnitRunner와 같은 버전의 Junit5 기능이다

     

    2) @InjectMocks: Mockito가 주도적으로 가짜객체를 주입하려고 시도한다. (스프링의 IoC와 비슷하지만 테스트를 위한 Mock 버전이라 생각하면 된다). 생성자를 이용한 객체 주입, Setter를 이용한 주입, 혹은 property를 이용한 주입을 이 순서로 시도한다. 이 시도중의 하나라도 실패한다고 해도, Mockito가 테스트가 실패한건 아니다. 다만, 개발자가 직접 dependency를 제공해야 한다.

     

    Mockito가 주입시켜준 객체를 사용하여 우리가 유닛테스트를 만들고 싶은 메소트를 호출하였다.

    dispatchService.validateLocations(l1, errorList);

    2. 테스트 구분용 어노테이션을 만들자

    DispatchServiceTest는 로직을 테스트 하였다. @LogicTest 라는 어노테이션을 만들고, 여기에 붙여준다.

    어노테이션을 만드는 자세한 글은 https://2ndprince.tistory.com/24를 참고한다

    package me.ndPrince.routeOptimization.annotation;
    
    import org.junit.jupiter.api.Tag;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Tag("Logic")
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LogicTest {
    }
    
    @ExtendWith(MockitoExtension.class)
    @LogicTest
    class DispatchServiceTest {
    
        @InjectMocks
        private DispatchService dispatchService;

     

    3. LogicTest를 위한 그레이들 Task 를 만들자

    자세한 내용은 해당 글 참고; https://2ndprince.tistory.com/23

     

    build.gradle에 아래의 task를 추가한다

    task logicTest(type: Test){
    	group "demo test"
    	useJUnitPlatform{
    		includeTags 'Logic'
    	}
    }
    

    4. 터미널을 이용하여 @LogicTest가 붙은 모든 클래스의 유닛테스트를 실행한다

    ./gradlew -q logicTest

    -q 옵션을 주면, 테스트 run이 실패했을때만 알려준다. BUILD SUCCESSFUL in 1s 같은 log 메세지를 보고 싶으면 -q를 지우고 입력한다. 

     

    5. 명령어 동작 확인

    위의 명령어가 정말 logicTest 어노테이션이 붙은 클래스를 테스트 했는지 확인하고 싶었다. 아마 더 좋은 방법이 있겠지만, 원시적으로, 일부러 앞서 만든 유닛테스르를 실패하게 assertEquals의 기대값을 1에서 2로 바꾸었다.

    실패를 통해 위의 명령어가 잘 작동함을 확인했다.

    어노테이션을 통한 테스트 구분은 어플리케이션이 커질수록 정말 필요한 기능이다.

    또한 명령어를 통해 실행 시킬 수 있으면, jenkins의 pipeline stage에서도 쉽게 실행 시킬 수 있다.

     

    완성된 코드

    https://github.com/2ndPrince/routeOptimization/tree/create-unit-test

     

    다음 시간에는 End-to-End 테스트를 구현해보자.

Designed by Tistory.