(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*方法来跟踪其内部的订阅和执行状态。

相关文章
相关标签/搜索