인프런 커뮤니티 질문&답변

이동기님의 프로필 이미지
이동기

작성한 질문수

Kevin의 알기 쉬운 Spring Reactive Web Applications: Reactor 1부

StepVerifier를 이용한 Testing (1)

StepVerifier를 이용한 Testing (1) - expectNoEvent 에 관해서

해결된 질문

작성

·

61

·

수정됨

1

안녕하세요! 강의 잘 듣고 있습니다. 다름이아니라

 

section10.class01.StepVerifierTimeBasedTestExample04 여기서 expectNoEvent 이 오퍼레이터가 시간을 기다려 주는 기능을 한다 이렇게 이해했는데 제가 조금 커스텀해서 찍어보다 보니 작동 방식이 도저히 이해가 되지 않아서 질문 남겼습니다.

 

아래 코드 처럼 1분마다 emit되는 예제에서 중구, 서초구는 1분씩 기다렸으니 순서대로 나오는게 맞는데 그 다음은 2분을 기다렸으니 강동구가 나와야 할 것 같았는데 강서구가 나오더라고요. expectNoEvent라는 오퍼레이터가 가지는 의미를 어떻게 이해야 할지 도저히 감이 안잡힙니다...ㅠ

 

[전체코드(테스트성공)]

public class StepVerifierTimeBasedTestExample04 {
    @Test
    public void getCOVID19CountTest() {
        StepVerifier
                .withVirtualTime(() -> TimeBasedExample.getVoteCount(
                                Flux.interval(Duration.ofMinutes(1)) // 1분마다 data를 emit
                        )
                )
                .expectSubscription()
                .expectNoEvent(Duration.ofMinutes(1)) // 1분동안 아무런 이벤트가 발생하지 않음
                .expectNext(Tuples.of("중구", 15400)) // 첫번째 튜플
                .expectNoEvent(Duration.ofMinutes(1))
                .expectNext(Tuples.of("서초구", 20020))
                .expectNoEvent(Duration.ofMinutes(1))
                .expectNoEvent(Duration.ofMinutes(1))
                .expectNext(Tuples.of("강서구", 32040))
                .expectNoEvent(Duration.ofMinutes(1))
                .expectNextCount(2)
                .expectComplete()
                .verify();
    }
}

 

[getVoteCount 함수]

public static Flux<Tuple2<String, Integer>> getVoteCount(Flux<Long> source) {
    return source
            .zipWith(Flux.just(
                                Tuples.of("중구", 15400),
                                Tuples.of("서초구", 20020),
                                Tuples.of("강서구", 32040),
                                Tuples.of("강동구", 14506),
                                Tuples.of("서대문구", 35650)
                            )
            )
            .map(Tuple2::getT2);
}

답변 1

1

Kevin님의 프로필 이미지
Kevin
지식공유자

안녕하세요?

expectNoEvent()의 의미가 조금 헷갈리시죠?

테스트 하셨던 코드처럼 .expectNoEvent(Duration.ofMinutes(1)) 를 두 번 연달아 호출했다고 해서 2분의 딜레이 타임을 가진다라는 의미가 아니라 매 1분(여기서는 두 번 호출했으니까 총 2분이겠죠) 즉, 총 2분 동안 단지 어떠한 이벤트도 발생하지 않았는지를 단순히 검증만 하는 것이라서 .expectNoEvent(Duration.ofMinutes(1)) 를 호출했다고 해서 getVoteCount()에서 리턴한 Flux의 Tuple 데이터가 emit 되지는 않고 예제 코드에서처럼 expectNext()를 호출해야 데이터 하나가 emit이 됩니다.

지금 테스트 해 보셨던 코드에서는

...
.expectNoEvent(Duration.ofMinutes(1)) 
.expectNoEvent(Duration.ofMinutes(1)) 
.expectNext(Tuples.of("강서구", 32040))
...
...

.expectNoEvent(Duration.ofMinutes(1)) 를 두 번 호출 한 다음에 호출한 .expectNext()가 세번째 .expectNext() 이기 때문에 "강서구"가 맞으므로 테스트에 통과하는 것입니다.

 

그리고 아직 테스트 대상 메서드의 Flux에서 emit되지 않은 데이터가 남았는데 expectNext()나 expectNextCount() 등으로 남아있는 데이터의 emit에 해당하는 onNext 이벤트를 트리거 해주지 않으면 테스트 코드가 종료되지 않고 무한정 기다릴 수 있는 상황도 발생할 수 있으니 참고해 주시면 좋을 것 같아요.

 

이동기님의 프로필 이미지
이동기
질문자

답변주신걸보면 또 이해가 되는 것 같으면서도 저만 이런건지 모르겠는데 테스트를 해보면 해볼수록 점점 더 이상하게 느껴지네요ㅠ 나중에 따로 공식 문서 참고해서 추가적인 확인을 해봐야겠습니다ㅎㅎ..

 

[실패]

.expectSubscription()
.expectNoEvent(Duration.ofMinutes(1))
.expectNext(Tuples.of("중구", 15400))
.expectNoEvent(Duration.ofMinutes(1))
.expectNext(Tuples.of("서초구", 20020))
.expectNoEvent(Duration.ofSeconds(30L)) // 차이점 1
.expectNoEvent(Duration.ofMinutes(1)) // 차이점 2
.expectNext(Tuples.of("강서구", 32040))
.expectNoEvent(Duration.ofSeconds(30L))
.expectNoEvent(Duration.ofMinutes(1))
.expectNextCount(2)
.expectComplete()
.verify();

 

[성공]

.expectSubscription()
.expectNoEvent(Duration.ofMinutes(1))
.expectNext(Tuples.of("중구", 15400))
.expectNoEvent(Duration.ofMinutes(1))
.expectNext(Tuples.of("서초구", 20020))
.expectNoEvent(Duration.ofMinutes(1)) // 차이점 1
.expectNoEvent(Duration.ofSeconds(30L)) // 차이점 2
.expectNext(Tuples.of("강서구", 32040))
.expectNoEvent(Duration.ofSeconds(30L))
.expectNoEvent(Duration.ofMinutes(1))
.expectNextCount(2)
.expectComplete()
.verify();

 

일단 개인적으로 이해한 것은

  1. 데이터가 5개니 총 5분 동안 흐른다

  2. interval 시간이 흐른다고 해서 자동으로 emit되는 것은 아니다.

  3. expectNext를 호출 하지 않는 이상 데이터는 emit되지 않지만 interval 시간은 흐른다.

     

 

그래도 러프하게는 가닥이 잡히는 것 같습니다. 답변 감사합니다!

 

Kevin님의 프로필 이미지
Kevin
지식공유자

첫 번째 테스트 케이스가 실패하는건 30초의 인터벌을 먼저 줌으로 인해서 결국 getVoteCount() 메서드에서 emit되는 데이터의 인터벌 시간과 어긋나기 때문에 이벤트가 없어야하는데 이벤트가 발생하기 때문에 실패하는 것 같습니다.

두 번째 테스트 케이스는 1분 인터벌 시간이 먼저 그 다음이 30초 인터벌인데, 공교롭게도 expectNext(Tuples.of("강서구", 32040)) 다음에 나오는 인터벌 시간이 30초, 1분이다보니 getVoteCount() 메서드에서의 인터벌 시간과 일치해서 테스트에 성공하는 것 같네요. ^^;

이동기님의 프로필 이미지
이동기
질문자

감사합니다! 이번엔 진짜 이해가 된거같습니다. 딴거는 한번에 이해 잘 하다가 여기서 미쳐버릴뻔했네요 ㅋㅋㅋㅋ

 

30초가 먼저 와버리는 바람에 1분의 인터벌 이벤트가 포함되어 이벤트가 발생해서 실패한거고

 

성공은 원래 대기하는 시간에 맞게 검증을 걸어서 넘어간건데 강서구 expectNext는.. 인터벌 쿨타임(?)을 만족하고 다음에 emit될 데이터가 맞으니 검증을 성공한거고

 

계속 곱씹어보면서 익숙해져야겠습니다..ㅠ

이동기님의 프로필 이미지
이동기

작성한 질문수

질문하기