본문 바로가기
Back-end/벡엔드

리액티브 프로그래밍 Spring WebFlux - 디버깅, 테스팅

by javapp 자바앱 2024. 8. 4.
728x90

 

디버깅

 

 

디버그 모드

        Hooks.onOperatorDebug();

 

비용이 많이 드는 동작 과정을 거치기 때문에 -> 처음부터 디버그 모드를 활성화하는 것은 권장하지 않음

 

 

checkpoint() 사용

Flux
    .just(2, 4, 6, 8)
    .zipWith(Flux.just(1, 2, 3, 0), (x, y) -> x/y)
    .checkpoint("Example12_4.zipWith.checkpoint", true)
    .map(num -> num + 2)
    .checkpoint("Example12_4.map.checkpoint", true)
    .subscribe(
            data -> log.info("# onNext: {}", data),
            error -> log.error("# onError:", error)
    );

실행 결과

Assembly trace from producer [reactor.core.publisher.FluxZip], described as [Example12_4.zipWith.checkpoint] :
reactor.core.publisher.Flux.checkpoint(Flux.java:3519)
org.example.webflux.FluxMain.main(FluxMain.java:16)
Error has been observed at the following site(s):
*__checkpoint(Example12_4.zipWith.checkpoint) ⇢ at org.example.webflux.FluxMain.main(FluxMain.java:16)
|_                                 checkpoint ⇢ Example12_4.map.checkpoint

 

 

 

 

 

 


 

 

 

 

테스팅

 

의존성 추가

    testImplementation 'io.projectreactor:reactor-test'

 

 

Signal 이벤트 테스트

public class StepVerifierGeneralTest {
    @Test
    public void sayHelloReactorTest() {
        StepVerifier
                .create(Mono.just("Hello world"))
                .expectNext("Hello world")
                .expectComplete()
                .verify();
    }
}

Flux 또는 Mono 를 Reactor Sequence 로 정의한 후, 구독 시점에 해당 Opeerator 체인이 시나리오대로 동작하는지 테스트

다음에 발생할 Signal 이 무엇인지, 기대하는 데이터들이 emit 되었는지 등

 

 

 

.as

public class GeneralTestExample {
    public static Flux<String> sayHello() {
        return Flux
                .just("Hello", "Reactor");
    }
@Test
public void sayHelloTest() {
    StepVerifier
            .create(GeneralTestExample.sayHello())
            .expectSubscription()
            .as("# expect subscription")
            .expectNext("Hi")
            .as("# expect Hi")
            .expectNext("Reactor")
            .as("# expect Reactor")
            .verifyComplete();
}

java.lang.AssertionError: expectation "# expect Hi" failed (expected value: Hi; actual value: Hello)

 

 

시간 기반 테스트

    @Test
    public void getCOVID19CountTest() {
        StepVerifier
                .create(TimeBasedTestExample.getCOVID19Count(
                                Flux.interval(Duration.ofSeconds(3)).take(1)
                        )
                )
                .expectSubscription()
                .expectNextCount(11)
                .expectComplete()
                .verify(Duration.ofSeconds(5));
    }

지정한 시간 내에 테스트 대상 메서드의 작업이 종료되는지를 확인

 

 

.withVirtualTime

@Test
public void getVoteCountTest() {
    StepVerifier
            .withVirtualTime(() -> TimeBasedTestExample.getVoteCount(
                            Flux.interval(Duration.ofMinutes(1))
                    )
            )
            .expectSubscription()
            .expectNoEvent(Duration.ofMinutes(1))
            .expectNoEvent(Duration.ofMinutes(1))
            .expectNoEvent(Duration.ofMinutes(1))
            .expectNoEvent(Duration.ofMinutes(1))
            .expectNoEvent(Duration.ofMinutes(1))
            .expectNextCount(5)
            .expectComplete()
            .verify();
}

.withVirtualTime() : StepVerifier 메서드 체인들이 VirtualTimeScheduler 의 제어를 받오록 함

.expectNoEvent(n) 지정된 시간 동안 어떤 이벤트도 발생하지 않을 것이라고 기대하는 동시에

지정한 시간만큼 시간을 앞당긴다는 것

 

 

StepVerifier

  • Reactor Sequence 에서 발생하는 Signal 이벤트를 테스트
  • .withVirtualTime() 과 VirtualTimeScheduler() 등을 이용해서 시간 기반 테스트 진행
  • .thenConsumeWhile() 을 이용해서 Backpressure 테스트 진행
  • .expectAccessibleContext()를 이용해서 Sequence에 전파되는 Context를 테스트 가능
  • .recordWith(), consumeRecordedWith(), expectRecordedMatches() 등을 이용해서 Record 기반 테스트 진행

 

그외

 

TestPublisher

개발자가 직접 Signal 을 발생시키면서 원하는 상황을 미세하게 재연하며 테스트 진행

 

PublisherProbe 

조건에 따라 Sequence가 분기되는 경우, Sequence 의 실행 경로를 추적해서 정상적으로 실행되었는지 테스트

  • PublisherProbe의 assertWasSubscribed(),  assertWasRequested(), assertWasNotCancelled() 등을 통해 Sequence의 기대하는 실행 경로를 Assertion 할 수 있음

 

 

 

댓글