本系列文章索引《响应式Spring的道法术器》
前情提要:Reactor 3快速上手 | 响应式流规范
本文测试源码
在非常重视DevOps的今天,以及一些奉行TDD的团队中,自动化测试是保证代码质量的重要手段。要进行Reactor的测试,首先要确保添加reactor-test
依赖。
reactor-test 用 Maven 配置 <dependencies>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.1.4.RELEASE</version>
<scope>test</scope>
</dependency>reactor-test 用 Gradle 配置
dependencies {
testCompile ‘io.projectreactor:reactor-test:3.1.4.RELEASE‘
}
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
,然后就可以进行以下测试:
expectNext(T...)
或expectNextCount(long)
。`consumeNextWith(Consumer<T>)
。thenAwait(Duration)
和then(Runnable)
。对于终止事件,相应的期望方法(如expectComplete()
、expectError()
,及其所有的变体方法) 使用之后就不能再继续增加别的期望方法了。最后你只能对 StepVerifier 进行一些额外的配置并 触发校验(通常调用verify()
及其变体方法)。
从StepVerifier
内部实现来看,它订阅了待测试的 Flux 或 Mono,然后将序列中的每个信号与测试 场景的期望进行比对。如果匹配的话,测试成功。如果有不匹配的情况,则抛出AssertionError
异常。
响应式流是一种基于时间的数据流。许多时候,待测试的数据流存在延迟,从而持续一段时间。如果这种场景比较多的话,那么会导致自动化测试运行时间较长。因此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
expectSubscription().expectNoEvent(duration)
来代替;verify
或变体方法最终会返回一个Duration
,这是实际的测试时长。可见,withVirtualTime使我们不用实际等1天来完成测试了。
虚拟时间的功能是如何实现的呢?StepVerifier.withVirtualTime
会在Reactor的调度器工厂方法中插入一个自定义的调度器VirtualTimeScheduler
来代替默认调度器(那些基于时间的操作符通常默认使用Schedulers.parallel()
调度器)。
通常情况下,使用StepVerifier
的expect*
就可以搞定多数的测试场景了。但是,它也有无计可施的时候,比如下边这个特殊的例子:
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
}
then()
会忽略所有的元素,只保留完成信号,所以返回值为Mono<Void>;1和2都是Mono<Void>
,这时候就比较难判断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
}
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*
方法来跟踪其内部的订阅和执行状态。
(16)Reactor的测试——响应式Spring的道法术器
原文地址:http://blog.51cto.com/liukang/2094939