iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
Software Development

從零開始Reactive Programming- Spring系列 第 19

[Day 18] Reactive Programming - Reactor Test(VirtualTime)

前言

接續上一篇介紹測試,之前也提到過Reactor提供VirtualTimeScheduler來讓測試更方便,現在就來結合StepVerifierVirtualTimeScheduler看看會有甚麼火花。

withVirtualTime

測試使用StepVerifier傳入publisher的方法,除了常用到的create來接收之外,withVirtualTime也是一個好方法,尤其是當你的情境是與時間有關的時候,從原始碼可以看到,有別於create是直接傳入一個publisherwithVirtualTime需要用Supplier包住publisher後再傳入,原因是時間相關的operators通常是透過Schedulers.parallel(),如果要使用虛擬時間就要用VirtualTimeScheduler去取代掉原本的Scheduler,所以VirtualTimeScheduler必須在operators初始化之前就已經啟動,也就是要延後operators初始化的時間,這句話基本上就等於lazy loading,所以為了讓虛擬時間的機制成功,必須在Supplier內初始化Flux,而不是在其他的地方。

static <T> FirstStep<T> withVirtualTime(Supplier<? extends Publisher<? extends T>> scenarioSupplier) {
 return withVirtualTime(scenarioSupplier, Long.MAX_VALUE);
}

StepVerifier提供了兩個方法讓你可以控制時間增加

  1. thenAwait(Duration):暫停期望評估(expectation evaluation),停下來等相對就是時間增加(可以順便反思人生不進則退,時光飛逝......)
  2. expectNoEvent(Duration):在傳入的Duration期間內預期沒有任何事情發生,相對也是一個時間推進的概念。要注意subscription也被視做一個event,如果一開始預期就沒有event發生,建議使用expectSubscription().expectNoEvent(duration) ,要不然都會是錯誤。
    要注意在使用withVirtualTime的同時必須要用到上述的兩種方法去增加時間,否則時間就將會停滯。
    https://ithelp.ithome.com.tw/upload/images/20211002/20141418osQCo94y78.png
圖片來源:網路

從官方提供的範例可以看到,遇到delay建議就使用expectSubscription先起手,避免subscription這個事件導致expectNoEvent失敗。透過VirtualTime推進了一天之後得到了一個資料0L,整個測試過程在真實世界只過了不到1秒。

StepVerifier.withVirtualTime(() -> Mono.delay(Duration.ofDays(1)))
    .expectSubscription()
    .expectNoEvent(Duration.ofDays(1))
    .expectNext(0L)
    .verifyComplete();

Question

在研究的時候找到一個stackoverflow的提問,提問者說,為何他的test如果不加上timeout的限制,就永遠不會停止,而如果加上了則會有exception發生。

   StepVerifier.withVirtualTime(() -> 
            Flux.just(1, 2, 3, 4).delayElements(Duration.ofSeconds(1)))
            .expectSubscription()
            .expectNextCount(4)
            .expectComplete()
            .verify(Duration.ofSeconds(10));

//java.lang.AssertionError: VerifySubscribertimed out on reactor.core.publisher.FluxConcatMap$ConcatMapImmediate@66d1af89

如果你有認真閱讀上面分享的內容並且吸收,你應該能看出問題點,為了避免暴雷,中間閒聊一下讓大家思考,我看完找出問題後往下確認,發現回答的人居然是Simon Baslé!也就是Project Reactor的頭頭,這是不是有點像是你問了一個萬有引力的問題,結果發現回答的人是牛頓。
https://ithelp.ithome.com.tw/upload/images/20211002/201414185pnPBOeyJ9.png

Answer

公布答案!答案就是他完全沒有用到任何會增加時間的方法,順利成為了讓時間停滯的男/女人,在上面的內容有提到如何增加時間,往上找一個時鐘的圖案就能發現了,最後附上Project Reactor的頭頭的精采回答

You need to use .thenAwait(Duration), otherwise the (virtual) clock won't move, and the delay never happens. You can also use .expectNoEvent(Duration) after the expectSubscription().
For example:

@Test
public void test() {
  StepVerifier.withVirtualTime(() -> 
        Flux.just(1, 2, 3, 4).delayElements(Duration.ofSeconds(1)))
        .expectSubscription() //t == 0
//move the clock forward by 1s, and check nothing is emitted in the meantime
        .expectNoEvent(Duration.ofSeconds(1))
//so this effectively verifies the first value is delayed by 1s:
        .expectNext(1)
//and so on...
        .expectNoEvent(Duration.ofSeconds(1))
        .expectNext(2)
//or move the clock forward by 2s, allowing events to take place,
//and check last 2 values where delayed
        .thenAwait(Duration.ofSeconds(2))
        .expectNext(3, 4)
        .expectComplete()
//trigger the verification and check that in realtime it ran in under 200ms
        .verify(Duration.ofMillis(200));
}

資料來源


上一篇
[Day 17] Reactive Programming - Reactor Test(StepVerifier)
下一篇
[Day 19] Reactive Programming - Reactor (operator fusion)
系列文
從零開始Reactive Programming- Spring32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言