Spring Boot

[Spring Boot]11. 리엑티브에서의 테스트 코드와 디버깅

진철 2024. 1. 2. 16:11
728x90
반응형
리액티브 프로그래밍 리뷰

리액티브 프로그래밍은 개발자가 분산 시스템에서 리소스를 더 잘 활용하게 해준다.

선언형으로 접근하기 때문에 기존의 명령형 자바를 사용하는데 익숙하기 때문에 처음에는 익숙하지 않을 것이다.

하지만 리액티브 자바는 명령형 자바에서 파생되었기 때문에 메커니즘은 유사하다.

특히나 리액티브 프로그래밍은 리액티브 스트림에 중점을 두었기 때문에 어려운 시스템 전체에서 확장이 가능하다. (물론 무조건 리액티브 프로그래밍을 사용해야한다는 것은 아니다. 리액티브 프로그래밍과 스프링 웹플럭스를 채택하는 것은 어디까지나 선택사항이다.)

 

굳이 @Controller+@ResponseBody를 사용하는 이유

명령형 자바에서는 @Controller와 @Responsebody를 합친 @RestController를 사용하였는데, 리액티브 프로그래밍에서는 이를 쓰지 않고 각각 분리해서 사용한다.

그 이유는 @RestController는 Object 값이 HTML 페이지의 DOM(도메인 객체 모델)을 통해 반환되는 것이 아니라 직접 반환되기 때문이다.

그리고 검색 옵션을 단일 방법으로 통합하고 사용자가 조회하였을 때, 하나가 아닌 여러 결과가 나올 수 있기에 Mono나 Flux가 아닌 Publisher를 반환한다.

Publisher는 Mono, Flux 두 타입을 모두 반환할 수 있다.

마지막으로 사용자가 검색 매개변수를 제공하지 않으면, Mono.empty()를 사용하여 비어 있는 Mono를 반환하게 예외처리를 해준다.

 

Repository를 받은 class의 메서드가 계속 빨간줄이 뜬다 ... -> interface 수정으로 유효한 메서드로 만들기

기존 PositionService의 repo.findAircraftByReg(reg) 메서드가 오류가 발생하는 것을 알 수가 있다.

하지만 이는 AircraftRepository 인터페이스에 Flux를 반환하는 메서드를 추가하면 문제를 해결할 수 있다.

이는 광범위하게 적용 가능한 규칙을 사용한 스프링 데이터의 쿼리 메소드 개념을 보여준다.

find, search, get 등과 같이 저장, 조회를 하는 객체에서 매개변수 등과 같은 타입을 반환하는 메소드 시그니처를 작성하면, 스프링 데이터는 메소드를 자동으로 구현한다. (코드에 더 자세한 정보를 제공하려면 @Query 어노테이션을 사용하면 된다.)

따라서 class 내에서 유효한 메서드로 인식하는 것이다.

 

리액티브 테스트하기

테스트를 위해서는 예상 결과를 설정하고, 실제 결과를 얻고, 비교해야하는 번거로운 과정들이 필요하다.

하지만 리액티브 스트림 Publisher는 완전한 결과 집합이 반환될 때까지 기다리지 않고 단일 단위로 반환한다.

이전 블로킹 타입의 Iterator는 5개의 결과를 한 번에 받는다면, 논블로킹 타입의 Publisher는 5개의 결과를 하나씩 개별로 빠르게 받는 것으로 이야기할 수 있다.

리액터 테스트 도구의 핵심은 StepVerifier이다.

StepVerifer는 Publisher를 구독하고 이름에서 알 수 있듯이 개발자가 개별적으로 얻은 값을 개별적으로 확인하게 한다.

 

디버깅

테스트를 통과했음에도 불구하고 실제 프로덕션에서 문제가 발생하면 디버깅을 사용해야한다.

디버깅은 코드가 실행되는 과정에서 어떠한 부분이 잘못되었는지를 알려주는 도구이다.

 

스택트레이스

일반적인 자바에서의 스택트레이스

일반적이 자바 어플리케이션에서 문제가 발생하면, 보통 스택트레이스가 사용된다.

이는 여러 이유로 명령형 코드로 순차적인 실행을 할 수 있으며, 해당 순차적 코드의 실행은 단일 스레드에서 발생한다.

해당 방법을 사용하면 일반적으로 전체 시스템 리소스를 효과적으로 활용하지 못할 수 있지만, 문제를 격리는데 유용하다.

 

리액티브 스트림에서의 스택트레이스

반면에 리액티브 스트림에서는 스케줄러를 사용하여 다른 스레드를 관리하고 사용한다.

그러나 이러한 기술은 단일 스레드의 활동을 단순히 따라가서는 실행되는 의미 있는 순차적 코드 목록을 생성하지 못한다는 문제가 발생한다.

스레드-호핑 최적화 기능으로 인하여 이미 실행 추적이 어려운데 더 복잡해지면, 리액티브 프로그래밍이 코드 조립과 코드 실행을 분리한다.

하지만 대부분의 Publisher 타입은 Subscribe 이전에는 아무일도 발생하지 않는다.

즉, Publisher(Mono or Flux) 파이프라인을 선언적으로 어셈블링한 코드에서 발생한 문제를 가리키는 프로덕션 에러는 마주할 가능성이 거의 없다.

 

로그를 효율적으로 확인하는 방법

오류는 보편적으로 값을 생성하거나 처리하거나 Subscriber에게 전달하는 등 파이프라인이 활성화되는 시점에서 발생한다.

오류를 확인하기 위해서 일반적인 스택트레이스를 사용하여 로그 값을 확인하면, 단 하나의 잘못된 정보에 대한 로그가 너무 많다는 문제점이 발생한다.

아래의 네 가지는 대표적인 디버깅 방법들이다.

Hooks

이 때 Hooks이라는 기능이 등장한다.

Hooks는 main 함수에서 사용되며, 주로 onOperatorDebug() 메서드가 사용된다. (물론, test 코드에서도 같이 사용된다.)

따라서 로그가 트리 형태로 출력되고 오류를 식별하고 해결하는 속도를 높인다.

Checkpoint

Hooks을 사용해도 여전히 너무 길고 비용이 많이 발생한다.

이는 모든 Publisher에 대한 로그를 출력하기 때문이다.

따라서 모든 것이 아닌 주요 Publisher 근처에 체크포인트를 설정해서 문제를 해결할 수 있다.

checkpoint() 메서드를 체인에 삽입하면, Hook을 사용하는 것 같지만, 해당 부분에서만 작동한다.

React-Tool

이는 Hook을 사용하여 생성한 백트레이스처럼 동일한 Hook을 사용해 디버깅을 활성화하는 방법이다.

성능 저하 없이 어플리케이션 내의 모든 Publisher 전체에 백트레이스의 이점을 실현할 수 있다는 장점이 있다.

디버깅 정보를 어플리케이션에 추가하고 실행 중인 어플리케이션에 연결하여 모든 후속 Publisher의 실행을 추적하며, 성능 저하 없이 동일한 종류의 자세한 백트레이스 정보를 Hook으로 제공한다.

따라서 ReactorDebugAgent를 활성화한 상태의 프로덕션 환경에서 리액티브 어플리케이션을 실행하기 때문에 단점이 거의 없다.

Block-Hound

블로킹 호출이 어플리케이션의 코드나 해당 의존성 내에 숨겨져 있는지 확인하는 데 유용하다.

728x90
반응형