API를 만드는 중에 JUnit으로는 Async로 된 메소드가 제대로 작동을 하지 않고 실제 프로그램을 구동시켰을 때 메소드가 정상적으로 작동하는걸 확인했다.
해당 상황을 재현해보고 분석해보는 시간을 가지겠다.
상황은 다음과 같다.
Controller에서 Service layer에 있는 메소드를 총 N 번 호출한다.
Service Layer의 해당 메소드는 비동기로 처리가 되고 해당 메소드는 0부터 100까지 Count를 한다.
매 카운트마다 100밀리초를 Thread.sleep을 한다.
사실상 코드는 아무 문제가 없어 보인다.
코드는 다음과 같다.
1. Controller
public class AdminController {
@Autowired
TestService testService;
@PostMapping("admin/test")
public ResponseEntity<?> testController(){
for(int i = 0 ; i < 5 ; i++){
testService.asyncMethod(i);
}
log.info("Main Process Done : " + LocalDateTime.now());
return ResponseEntity.ok().body(LocalDateTime.now());
}
}
2. Service
public class TestService {
@Async
public void asyncMethod(int threadNumber){
try{
for(int i = 0 ; i < 100 ; i ++){
log.error(threadNumber + "th thread count :" + i);
Thread.sleep(100);
}
log.info("Thread " + threadNumber + " Complete : " + LocalDateTime.now());
}catch(InterruptedException e){
log.info(e.getMessage());
}
}
}
3. Test
@SpringBootTest
@AutoConfigureMockMvc
class AdminControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void testAsyncProcess() throws Exception {
mockMvc.perform(post("/admin/test")//
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andDo(print());
}
}
> 메소드에서 찍은 값을 보다 잘 볼 수 있게 하기 위해 log.error로 찍게 되었다.
이제 Test를 돌렸을 때 Console에 찍힌 결과이다.
전체를 보여줄 수는 없고 먼저 Thread가 값을 세는 부분은 다음과 같다.
저게 끝이다.
그 이후에
이렇게 Test가 끝난다.
즉, Service Layer의 Async한 메소드는 Thread sleep 도중 Interrupt를 받아 강제 종료가 된다.
다음은 PostMan으로 이를 실험할 때의 상황이다.
다음과 같이 메소드가 정상적으로 마무리 되었을 때 Log값이 출력이 된다. 그리고 값도 PostMan이 정상적으로 받은 것을 확인 할 수 있다.
Async는 멀티 쓰레드로 동작한다. 따라서 한 프로세스가 종료 되거나 Thread에 Interrupt가 걸리지 않는 이상 Async로 선언된 메소드가 방해받을 일은 없다.
하지만 JUnit은 프로세스가 종료되거나 라는 가정에 해당된다.
JUnit Test는 자신의 MockMvc가 perform을 하고 정상 응답을 받으면 정상적으로 테스트가 완료되었다고 생각하고 프로세스를 종료시킨다. 하지만 Async로 메소드를 만들었을 때 응답은 비동기 메소드가 실행 되는 중에도 응답을 반환할 수 있다. 따라서 이러한 상황에서는 JUnit이 프로세스를 종료 시켜 버리고 해당 프로세스에 비동기적으로 실행되고 있던 다른 Thread가 Interrupt를 받아 종료가 된다.\
긴 글 읽어주셔서 감사합니다.
틀린 부분이 있으면 댓글을 달아주시면 감사하겠습니다.
📧 : may3210@g.skku.edu
'개발 > Spring' 카테고리의 다른 글
[Spring ] SLF4J을 이용한 스프링 로그 작성 (0) | 2022.02.15 |
---|---|
[Spring]JUnit에서 Async메소드의 비정상적인 종료 - 2편 (0) | 2022.02.15 |
[Spring] Rest Docs 빌드 부터 사용까지 (2) | 2022.01.24 |
[Spring] Spring과 Paging - [1] (0) | 2022.01.20 |
[Spring] Spring Security의 이해 (0) | 2022.01.19 |