(16)Reactor的測試——響應式Spring的道法術器

本系列文章索引《響應式Spring的道法術器》
前情提要:Reactor 3快速上手 | 響應式流規範
本文測試源碼java

2.6 測試

在很是重視DevOps的今天,以及一些奉行TDD的團隊中,自動化測試是保證代碼質量的重要手段。要進行Reactor的測試,首先要確保添加reactor-test依賴。react

reactor-test 用 Maven 配置 <dependencies>git

<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.1.4.RELEASE</version>
<scope>test</scope>
</dependency>github

reactor-test 用 Gradle 配置app

dependencies {
testCompile 'io.projectreactor:reactor-test:3.1.4.RELEASE'
}ide

2.6.1 使用 StepVerifier 來測試

1.3.2.3節初步介紹了關於StepVerifier的用法。舉個例子回憶一下:測試

@Test
    public void testAppendBoomError() {
        Flux<String> source = Flux.just("foo", "bar");

        StepVerifier.create(
                appendBoomError(source))
                .expectNext("foo")
                .expectNext("bar")
                .expectErrorMessage("boom")
                .verify();
    }

咱們一般使用create方法建立基於Flux或Mono的StepVerifier,而後就能夠進行如下測試:線程

  • 測試指望發出的下一個信號。若是收到其餘信號(或者信號與指望不匹配),整個測試就會 失敗(AssertionError),如expectNext(T...)expectNextCount(long)。`
  • 處理(consume)下一個信號。當你想要跳過部分序列或者當你想對信號內容進行自定義的校驗的時候會用到它,可使用consumeNextWith(Consumer&lt;T&gt;)
  • 其餘操做,好比暫停或運行一段代碼。好比,你想對測試狀態或內容進行調整或處理, 你可能會用到thenAwait(Duration)then(Runnable)

對於終止事件,相應的指望方法(如expectComplete()expectError(),及其全部的變體方法) 使用以後就不能再繼續增長別的指望方法了。最後你只能對 StepVerifier 進行一些額外的配置並 觸發校驗(一般調用verify()及其變體方法)。code

StepVerifier內部實現來看,它訂閱了待測試的 Flux 或 Mono,而後將序列中的每一個信號與測試 場景的指望進行比對。若是匹配的話,測試成功。若是有不匹配的狀況,則拋出AssertionError異常。blog

響應式流是一種基於時間的數據流。許多時候,待測試的數據流存在延遲,從而持續一段時間。若是這種場景比較多的話,那麼會致使自動化測試運行時間較長。所以StepVerifier提供了能夠操做「虛擬時間」的測試方式,這時候須要使用StepVerifier.withVirtualTime來構造。

爲了提升 StepVerifier 正常起做用的機率,它通常不接收一個簡單的 Flux 做爲輸入,而是接收 一個Supplier,從而能夠在配置好訂閱者以後 「懶建立」待測試的 flux,如:

StepVerifier.withVirtualTime(() -> Mono.delay(Duration.ofDays(1)))
//... 繼續追加指望方法

有兩種處理時間的指望方法,不管是否配置虛擬時間都是可用的:

  • thenAwait(Duration)會暫停校驗步驟(容許信號延遲發出)。
  • expectNoEvent(Duration)一樣讓序列持續必定的時間,期間若是有任何信號發出則測試失敗。

在普通的測試中,兩個方法都會基於給定的持續時間暫停線程的執行。而若是是在虛擬時間模式下就相應地使用虛擬時間。

StepVerifier.withVirtualTime(() -> Mono.delay(Duration.ofDays(1)))
        .expectSubscription()   // 1
        .expectNoEvent(Duration.ofDays(1))  // 2
        .expectNext(0L)
        .verifyComplete();  // 3
  1. expectNoEvent 將訂閱(subscription)也認做一個事件。假設你用它做爲第一步,若是檢測 到有訂閱信號,也會失敗。這時候可使用expectSubscription().expectNoEvent(duration) 來代替;
  2. 期待「一天」內沒有信號發生;
  3. verify或變體方法最終會返回一個Duration,這是實際的測試時長。

可見,withVirtualTime使咱們不用實際等1天來完成測試了。

虛擬時間的功能是如何實現的呢?StepVerifier.withVirtualTime會在Reactor的調度器工廠方法中插入一個自定義的調度器VirtualTimeScheduler來代替默認調度器(那些基於時間的操做符一般默認使用Schedulers.parallel()調度器)。

2.6.2 用 PublisherProbe 檢查執行路徑

一般狀況下,使用StepVerifierexpect*就能夠搞定多數的測試場景了。可是,它也有機關用盡的時候,好比下邊這個特殊的例子:

private Mono<String> executeCommand(String command) {
        // 基於command執行一些操做,執行完成後返回Mono<String>
    }

    public Mono<Void> processOrFallback(Mono<String> commandSource, Mono<Void> doWhenEmpty) {
        return commandSource
                .flatMap(command -> executeCommand(command).then())     // 1
                .switchIfEmpty(doWhenEmpty);    // 2
    }
  1. then()會忽略全部的元素,只保留完成信號,因此返回值爲Mono<Void>;
  2. 也是一個Mono<Void>。

1和2都是Mono&lt;Void&gt;,這時候就比較難判斷processOfFallback中具體執行了哪條路徑。這時候能夠用log()doOn*()等方法來觀察,但這「在非綠即紅」的單測中不起做用。或者在某個路徑加入標識狀態的值,並經過判斷狀態值是否正確來肯定,但這就須要修改被測試的processOfFallback的代碼了。

Reactor版本 3.1.0 以後咱們可使用PublisherProbe來作相似場景的驗證。以下:

@Test
    public void testWithPublisherProbe() {
        PublisherProbe<Void> probe = PublisherProbe.empty();    // 1

        StepVerifier.create(processOrFallback(Mono.empty(), probe.mono()))  // 2
                    .verifyComplete();

        probe.assertWasSubscribed();    // 3
        probe.assertWasRequested();     // 4
        probe.assertWasNotCancelled();  // 5
    }
  1. 建立一個探針(probe),它會轉化爲一個空序列。
  2. 在須要使用 Mono<Void> 的位置調用 probe.mono() 來替換爲探針。
  3. 序列結束以後,你能夠用這個探針來判斷序列是如何使用的,你能夠檢查是它從哪(條路徑)被訂閱的…​
  4. 對於請求也是同樣的…​
  5. 以及是否被取消了。

2.6.3 使用TestPublisher手動發出元素

TestPublisher本質上是一個Publisher,不過使用它能更加「自由奔放」地發出各類元素,以便進行各類場景的測試。

1)「自由」地發出元素

咱們能夠用它提供的方法發出各類信號:

  • next(T) 以及 next(T, T...) 發出 1-n 個 onNext 信號。
  • emit(T...) 起一樣做用,而且會執行 complete()。
  • complete() 會發出終止信號 onComplete。
  • error(Throwable) 會發出終止信號 onError。

好比:

@Test
    public void testWithTestPublisher() {
        TestPublisher<Integer> testPublisher = TestPublisher.<Integer>create().emit(1, 2, 3);
        StepVerifier.create(testPublisher.flux().map(i -> i * i))
                .expectNext(1, 4, 9)
                .expectComplete();
    }

2)「奔放」地發出元素

使用create工廠方法就能夠獲得一個正常的TestPublisher。而使用createNonCompliant 工廠方法能夠建立一個「不正常」的TestPublisher。後者須要傳入由TestPublisher.Violation 枚舉指定的一組選項,這些選項可用於告訴 publisher 忽略哪些問題。枚舉值有:

  • REQUEST_OVERFLOW: 容許 next 在請求不足的時候也能夠調用,而不會觸發 IllegalStateException。
  • ALLOW_NULL: 容許 next 可以發出一個 null 值而不會觸發 NullPointerException。
  • CLEANUP_ON_TERMINATE: 能夠重複屢次發出終止信號,包括 complete()、error() 和 emit()。

不過這個功能可能更多地是給Reactor項目開發者自己使用的,好比當他們開發了一個新的操做符,能夠用這種方式來測試這個操做符是否知足響應式流的規範。

3)TestPublisher也是個PublisherProbe

更讚的是,TestPublisher實現了PublisherProbe接口,意味着咱們還可使用它提供的assert*方法來跟蹤其內部的訂閱和執行狀態。

相關文章
相關標籤/搜索