-
2. 인텔리제이(IDE)를 써서 효과적으로 디버깅 할 수 있다스프링개발자/202 - 디버깅+테스팅 2020. 8. 14. 00:59
본 포스팅에 사용되는 코드베이스(204 Validation 시리즈에서 만든 코드이다)
https://github.com/2ndPrince/routeOptimization/pull/8
1. 문제 발생
어플리케이션을 실행시키고 아래의 json request를 postman을 이용하여 "/dispatch" endpoint에 전송하자
package me.ndPrince.routeOptimization; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RouteOptimizationApplication { public static void main(String[] args) { SpringApplication.run(RouteOptimizationApplication.class, args); } }
{ "id": "1", "rides": [ { "rideId": "1", "pickup": { "latitude": -999.9, "longititude": 0.2 }, "dropOff": { "latitude": 0.1, "longititude": 535.5 }, "capacity": 5 } ] }
결과는 다음과 같다
2. 디버깅 첫번째 스텝, run메뉴
500에러는 user request의 잘못이 아니라, 내 프로그램에 문제가 있다는 의미이다.
1차적인 잘못은 user request에 있을 수도 있지만, 그럼에도 프로그램은 400 status 를 리턴 할 수 있도록 보완되어야 한다.
첫번째로 볼 곳은 intellij의 run메뉴
java.lang.NullPointerException: null at me.ndPrince.routeOptimization.DispatchService.validateLocations(DispatchService.java:33) ~[main/:na] at me.ndPrince.routeOptimization.DispatchService.lambda$validate$1(DispatchService.java:22) ~[main/:na] at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na] at me.ndPrince.routeOptimization.DispatchService.validate(DispatchService.java:22) ~[main/:na] at me.ndPrince.routeOptimization.DispatchController.postDispatchRequest(DispatchController.java:22) ~[main/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.2.7.RELEASE.jar:5.2.7.RELEASE]
error trace가 있다.
1) 내 어플리케이션이 구현한 클래스들은 파란색으로 나온다.
2) 보통?항상? trace의 시작은 Thread이다
3) 가장 위에 나온 파란색이, 최종적으로 잘못된 내 코드의 부분이다. 라인넘버까지 알려준다. 33번째줄
3. 에러 trace 디버깅
java.lang.NullPointerException: null at me.ndPrince.routeOptimization.DispatchService.validateLocations(DispatchService.java:33) ~[main/:na] at me.ndPrince.routeOptimization.DispatchService.lambda$validate$1(DispatchService.java:22) ~[main/:na] at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na] at me.ndPrince.routeOptimization.DispatchService.validate(DispatchService.java:22) ~[main/:na] at me.ndPrince.routeOptimization.DispatchController.postDispatchRequest(DispatchController.java:22)
가장 아래쪽에 위치한 파란색부터 시작하는것이 전체적인 문제 이해를 위해 좋다.
DispatchController의 22번째 라인이 호출되었다
우리 RestController가 가장 먼저 호출되는 클래스였다. 22번째 줄에 json request 가 dispatchService로 감을 알 수 있다.
DispatchService의 22번째 라인이 호출 되었다
trace에 따르면, 결국에 33번째 라인이 호출되었는데, 인은 validateLocations 메소드 안에 로직이다;
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)); }
isLonValid가 33번째 줄인데, 여기서 nullPointException이 발생했다.
여기까지만 보고 문제가 뭔지 안다면, 코드를 고치면 되는데, 문제를 모르겠다.
4. Breakpoint 만들기
위의 33번째 줄에 디버깅 포인트를 잡고, 어플리케이션을 디버깅 모드로 실행시킨다.
postman request를 전송하면, 여기에 장면이 멈춘다.
1) 왼쪽 아래에 저장된 frame들을 참고한다; error trace에서 보았던 dispatchController단까지 올라갈 수 있다.
2) Variable tab을 참고한다
longitutde값이 null이었다.
어라, 분명히 전달했는데, 하고 json request를 확인해본다.
스펠링 오타가 있었다. 문제를 이렇게 찾으면 편하고, 그래도 모른다면 Variables tab위에 있는 계산기 모양 버튼을 이용한다.
5. 계산기 디버깅 기능(Evaluate)
해당 장면(frames)의 궁금한 variable값들을 직접 쳐볼 수 있고, 내가 만들어 볼 수도 있다.
Evaluate기능을 이용하여 java stream같은 것을 좀 더 쉽게 구현(Simulate) 해볼 수 있다.
현재 frame의 다음 line으로 가고 싶다면 F8혹은 step over 버튼을 클릭한다. Step into, Step out기능 모두 익숙해지면 디버깅에 큰 도움을 준다.
6. 이후의 디버깅
디버거를 통해서도 문제를 발견하지 못했으면 보통 다음의 절차를 따른다.
1) 프로그램과 관련된 셋팅을 확인한다 (database가 up running인지, 올바른 git branch 인지)
2) 기계는 멍청할 순 있지만, 거짓말은 안한다. app 혹은 IDE를 다시 시작해본다.
3) 인터넷 stackoverflow 등의 커뮤니티 확인
4) 동료에게 물어보기
5) 조금 쉬었다가 다음에 다시 디버깅하기
'스프링개발자 > 202 - 디버깅+테스팅' 카테고리의 다른 글
5. Java + Selenium 테스트를 만들 수 있다 (0) 2020.08.15 4. End-to-End 테스트를 만들 수 있다 (0) 2020.08.15 3. 유닛 테스트를 어노테이션과 함께 만들 수 있다 (0) 2020.08.14 1. Auto-configure 을 이해하고 디버깅 할 수 있다 (0) 2020.08.14 스프링개발자 202 (0) 2020.07.14