ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 4. End-to-End 테스트를 만들 수 있다
    스프링개발자/202 - 디버깅+테스팅 2020. 8. 15. 04:42

    유닛테스트는 클래스 안의 메소드 하나 정도를 테스트 한 작은 단위의 테스트라면, End-to-End 테스트 (줄여서 E2E) 는 모든 영역에 걸쳐 어플리케이션이 잘 작동하는지, client의 입장에서 구현한 테스트이다. 앞서 Postman을 통해 request를 전송하고, response가 잘 나왔는지 검증하는 작업을, 아주 그대로 코드를 통해 구현하는 것이라 생각하면 쉽다.

     

    따라서 해보려면, 이전 글의 완성한 아래의 branch에서 작업 시작하면 된다.

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

     

    1. EndToEndTest 어노테이션을 만들자

    package me.ndPrince.routeOptimization.annotation;
    
    import org.junit.jupiter.api.Tag;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Tag("EndToEnd")
    @Retention(RetentionPolicy.RUNTIME)
    public @interface EndToEndTest {
    }
    

     

    2. RequestSpecification 그레이들 의존성 추가

    testImplementation 'io.rest-assured:rest-assured'

    postman에서 request를 전송 하듯이, 우리는 RequestSpecification를 사용하여 동일한 효과를 낸다.

    contentType, accept, oauth2, header, body 등을 쉽게 명시하여 request를 만들 수 있다.

     

    3. Request Body를 만든다

    postman의 json request를 문자열 그대로 복사 붙여넣기 한다. Ctrl+C and Ctrl+V 하면 알아서 줄바꿈 포멧 적용된다

    package me.ndPrince.routeOptimization;
    
    public class EndToEndTestUtil {
    
        public static String normalRequest(){
            return "{\n" +
                    "    \"id\": \"1\",\n" +
                    "    \"rides\": [\n" +
                    "        {\n" +
                    "            \"rideId\": \"1\",\n" +
                    "            \"pickup\": {\n" +
                    "                \"latitude\": -9.9,\n" +
                    "                \"longitude\": 0.2\n" +
                    "            },\n" +
                    "            \"dropOff\": {\n" +
                    "                \"latitude\": 0.1,\n" +
                    "                \"longitude\": 35.5\n" +
                    "            },\n" +
                    "            \"capacity\": 5\n" +
                    "        }\n" +
                    "    ]\n" +
                    "}";
        }
    }
    

    4. DispatchResponse를 수정하자

    package me.ndPrince.routeOptimization.model;
    
    import lombok.Builder;
    import lombok.Data;
    
    import javax.validation.constraints.NotNull;
    import java.util.List;
    
    @Data
    @Builder
    public class DispatchResponse {
    
        @Builder.Default
        @NotNull
        protected String id;
    
        private List<VehicleScheduleResponse> vehicleSchedules;
        private List<Double> dummyList;
    }
    

    5. DispatchController를 수정하자

    @RestController
    @RequestMapping(value = "/dispatch")
    public class DispatchController {
    
        @Autowired
        private DispatchService dispatchService;
    
        @PostMapping
        public DispatchResponse postDispatchRequest(@Valid @RequestBody DispatchRequest request) {
            ArrayList<Double> dummyLists = new ArrayList<>();
            dummyLists.add(5.3);
            dummyLists.add(7.9);
            dummyLists.add(0.4);
    
            dispatchService.validate(request);
            return DispatchResponse.builder()
                    .id("1")
                    .dummyList(dummyLists)
                    .build();
        }
    }

    empty DispatchResponse를 리턴하지 않고, 내용을 채워서 리턴한다. 현재 코드는 request에 상관없이 일정한 response를 반환하지만, 실제로 서비스를 구현할때는, request에 따른 적절한 response를 리턴하도록 service, repo 등등을 구현하자.

     

    6. DispatchEndToEndTest 클래스를 만든다

    package me.ndPrince.routeOptimization;
    
    import io.restassured.http.ContentType;
    import io.restassured.response.Response;
    import io.restassured.specification.RequestSpecification;
    import me.ndPrince.routeOptimization.annotation.EndToEndTest;
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.mockito.junit.jupiter.MockitoExtension;
    
    import static io.restassured.RestAssured.given;
    import static org.junit.Assert.assertEquals;
    
    @ExtendWith(MockitoExtension.class)
    @EndToEndTest
    class DispatchEndToEndTest {
    
        private RequestSpecification requestSpecification;
        private String dispatchUrl = "http://localhost:8080/dispatch";
    
        @BeforeEach
        void setUp(){
            requestSpecification = given().contentType(ContentType.JSON).accept(ContentType.JSON);
        }
    
        @Test
        void dispatchRequest_basic() throws JSONException {
    
            Response response = requestSpecification
                    .body(EndToEndTestUtil.normalRequest())
                    .post(dispatchUrl);
            response.then().assertThat().statusCode(200);
    
            JSONObject jsonObject = new JSONObject(response.getBody().jsonPath().prettyPrint());
            // JSONArray vehicleSchedules = jsonObject.getJSONArray("vehicleSchedules"); // can't extract from null
            JSONArray dummyList = jsonObject.getJSONArray("dummyList");
            String id = jsonObject.getString("id");
    
            assertEquals(3, dummyList.length());
            assertEquals("1", id);
        }
    
    }

    1) dispatchUrl에 http://가 반드시 포함되어야 한다 그렇지 않으면 404 에러 난다.
    2) BeforeEach 어노테이션을 통해, 우리 request를 매번 새로고침 시켜준다.

    3) requestSpecification에 전달할 수 있는 필드값들이 많다. (header, proxy, filter, baseUri, httpsvalidation 등)

    4) request body로 위에서 만든 json 문자열을 넘겨준다.

    5) setUp에서 로컬환경이 아닌, 클라우드 환경에서 oauth2 시큐리티 문제가 있다면, given().auth().oauth2(getYourToken()) 식으로 토큰또한 필드값으로 넘겨준다.

    6) response값도 postman 처럼 json으로 반환되는데, 이걸 org.json library를 통해 잘 parse하자

    7) jsonObject를 parsing 할때에, null 혹은 존재하지 않은 필드 값을 넘겨주면, nullPointException이 난다(주석처리 된 vehicleSchedules처럼)

     

    7. 만든 E2E 테스트를 실행해본다

    유닛테스트처럼 테스트만 실행해서는 안되고, 어플리케이션을 동작 시키고, E2E테스트를 그 위에 시작해야 한다. E2E 테스트가 localhost:8080에 직접 호출을 하기 때문이다. 정말 postman사용해서 하는 테스트와 똑같다.

     

    테스트가 패스했다. status 200이 나왔으며, assertEquals로 명시한 조건들이 전부 맞았다.

     

    운영팁으로, E2E테스트 만들 위한 젠킨스 프로젝트를 따로 두자. 메인프로젝트 젠킨스가 success될때, E2E테스트 젠킨스 프로젝트가 trigger되서 자동시작되도록 만든다.

     

    이정도면 실전 프로그램에 쓰이는 테스트를 꽤 커버 한거 같다. 다음 글에서는 이 카테고리 마지막으로, selenium을 이용하여 front-end를 자동테스트 해보자.

     

    완성된 코드는 아래 링크에서 볼 수 있다.

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

     

     

     

     

     

Designed by Tistory.