附录 E:RS TCK 测试方法参考

按 reactive-streams-tck 1.0.4 实际源码整理四类验证基类:PublisherVerification 38 个用例(22 required)、SubscriberWhiteboxVerification 27 个(14 required)、SubscriberBlackboxVerification 26 个(11 required)、IdentityProcessorVerification 68 个(38 required)。逐条列出真实方法名、对应规范条款、抽象方法签名与被标记为 untested 的规则,并给出 TestNG 运行方式、Flow 适配写法和实测通过/跳过计数。

先把边界说清楚

Reactive Streams 1.0.4 有 43 条规则,TCK 并没有为每一条都写断言。它只提供四类基类,用例由 TestNG 驱动;方法名前缀直接表示覆盖强度:

前缀含义统计时怎么算
required_必测;失败即不合规必须通过
optional_与可选能力相关;不具备该能力时会跳过或失败单独说明
stochastic_随机化重复测试,可用 skipStochasticTests() 关闭通过/跳过都要记录
untested_TCK 尚未实现断言,运行时抛 SkipException不算通过

TCK 1.0.4 的 POM 直接依赖 TestNG 7.3.0;依赖树里还会出现 JUnit 4.12,那是 TestNG 自己的 compile 依赖,不代表 TCK 用例是 JUnit 用例。运行入口是 org.testng.TestNG,不是 JUnit Runner。

四类基类的真实规模

数量按 1.0.4 源码逐个方法统计:

基类验证对象requiredoptionalstochasticuntested其他合计
PublisherVerification<T>Publisher22817038
SubscriberWhiteboxVerification<T>Subscriber(可控制订阅)140013027
SubscriberBlackboxVerification<T>Subscriber(黑盒)110015026
IdentityProcessorVerification<T>Processor388120168

IdentityProcessorVerification 的「其他」是 mustImmediatelyPassOnOnErrorEventsReceivedFromItsUpstreamToItsDownstream,对应 §4.2 的「不恢复就必须立即向下游传播 onError」。

规则覆盖到什么程度

这张表回答的是「某条规则到底有没有自动化断言」,而不是「实现是否合规」。同一个规则可能同时有 required、optional 和 untested 三种形态。

规则覆盖对应测试
§1.1 发射量不超过请求量requiredspec101
§1.2 可以少发并终止requiredspec102
§1.3 信号串行stochasticspec103
§1.4 失败必须 onErroroptionalspec104
§1.5 有限流必须 onCompleterequired + optionalspec105、optional_spec105_*
§1.6 终止后订阅视为取消untestedspec106
§1.7 终止后不得再发信号required + untestedspec107 两条
§1.8 取消后最终停止untestedspec108
§1.9 onSubscribe 先行与拒绝语义requiredspec109 三条
§1.10 同一 Subscriber 不得重复订阅untestedspec110
§1.11 多订阅者能力optionalspec111 五条
§2.1 必须通过 request 表达需求requiredspec201
§2.2 建议异步派发untestedspec202
§2.3 终止回调不得操作订阅requiredspec203 两条
§2.4 终止后视为已取消untestedspec204
§2.5 重复 onSubscribe 取消新订阅requiredspec205
§2.6 不再需要时取消untestedspec206
§2.7 request/cancel 串行调用untestedspec207
§2.8 取消后仍可能收到 onNextrequiredspec208
§2.9 无请求也要能收 onCompleterequiredspec209 两条
§2.10 无请求也要能收 onErrorrequiredspec210 两条
§2.11 信号 happens-beforeuntestedspec211
§2.12 onSubscribe 最多一次untestedspec212
§2.13 null 参数抛 NPErequired + untestedspec213 四条
§3.1 只在 Subscriber 上下文调用untestedspec301
§3.2 允许同步 requestrequiredspec302
§3.3 同步递归有上界requiredspec303、required_validate_boundedDepth...
§3.4 request 及时返回untestedspec304
§3.5 cancel 不同步做重计算untestedspec305(TCK 未实现断言)
§3.6 取消后 request 是 NOPrequiredspec306
§3.7 取消后 cancel 是 NOPrequiredspec307
§3.8 request 累加需求requiredspec308
§3.9 非正请求必须 onErrorrequired + optionalspec309 三条
§3.10 request 可同步 onNextuntestedspec310
§3.11 request 可同步终止untestedspec311
§3.12 取消后最终停止发射requiredspec312
§3.13 取消后释放 Subscriber 引用requiredspec313
§3.14 取消可使 Publisher 关闭untestedspec314
§3.15 cancel 不得抛异常untestedspec315
§3.16 request 不得抛异常untestedspec316
§3.17 支持 Long.MAX_VALUE 需求requiredspec317 三条
§4.1 Processor 同时遵守两侧契约委托Publisher + Subscriber 两套用例
§4.2 Processor 不恢复时必须传播 onError专用用例mustImmediatelyPassOn...

结论很直接:§1.6、§1.8、§1.10、§2.4、§2.6、§2.7、§2.11、§2.12、§3.1、§3.4、§3.5、§3.10、§3.11、§3.14、§3.15、§3.16 在 1.0.4 中没有 required 断言。TCK 全绿只说明「被检查的部分」合规,这些条款需要自己补测试或做代码审查。

PublisherVerification

必须实现的抽象方法

public abstract Publisher<T> createPublisher(long elements);
public abstract Publisher<T> createFailedPublisher();

两个方法的语义:

  • createPublisher(long elements) 返回的 Publisher 必须恰好发射 elements 个元素,并在之后终止。TCK 至少会用 1 和 3 调用它,所以「永远只发一个元素」的实现不能直接拿来跑。
  • createFailedPublisher() 返回 null 表示跳过失败路径用例;返回实例时,该实例必须在 onSubscribe 之后发 onError,而不是直接抛异常。

可选覆写:

public long maxElementsFromPublisher() { return Long.MAX_VALUE - 1; }          // 默认值
public long boundedDepthOfOnNextAndRequestRecursion() { return 1; }            // 默认值
public boolean skipStochasticTests() { return false; }                         // 默认值

maxElementsFromPublisher() 的默认值是 Long.MAX_VALUE - 1,不是 Long.MAX_VALUE。返回 Long.MAX_VALUE 会让依赖于「有限流」的用例直接跳过。

22 个必测方法

配置与元素数:

方法验证内容
required_createPublisher1MustProduceAStreamOfExactly1ElementcreatePublisher(1) 恰好 1 个元素
required_createPublisher3MustProduceAStreamOfExactly3ElementscreatePublisher(3) 恰好 3 个元素
required_validate_maxElementsFromPublishermaxElementsFromPublisher() >= 0
required_validate_boundedDepthOfOnNextAndRequestRecursion递归深度配置 >= 1

§1 发布侧:

方法规则
required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements§1.1
required_spec102_maySignalLessThanRequestedAndTerminateSubscription§1.2
required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates§1.5
required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled§1.7
required_spec109_subscribeThrowNPEOnNullSubscriber§1.9
required_spec109_mustIssueOnSubscribeForNonNullSubscriber§1.9
required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe§1.9

§3 订阅侧:

方法规则
required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe§3.2
required_spec303_mustNotAllowUnboundedRecursion§3.3
required_spec306_afterSubscriptionIsCancelledRequestMustBeNops§3.6
required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops§3.7
required_spec309_requestZeroMustSignalIllegalArgumentException§3.9
required_spec309_requestNegativeNumberMustSignalIllegalArgumentException§3.9
required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling§3.12
required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber§3.13
required_spec317_mustSupportAPendingElementCountUpToLongMaxValue§3.17
required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue§3.17
required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue§3.17

可选与未测

8 个 optional 方法:

方法触发条件
optional_spec104_mustSignalOnErrorWhenFailscreateFailedPublisher() 非 null
optional_spec105_emptyStreamMustTerminateBySignallingOnCompletecreatePublisher(0) 能正常完成
optional_spec111_maySupportMultiSubscribe支持多订阅
optional_spec111_registeredSubscribersMustReceiveOnNextOrOnCompleteSignals多订阅者都能收到信号
optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne多播且逐个请求
optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront多播且一次性请求
optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected多播、一次性请求且能完成
optional_spec309_requestNegativeNumberMaySignalIllegalArgumentExceptionWithSpecificMessage负数请求的错误消息足够具体

7 个 untested 方法会直接跳过:spec106(终止后订阅视为取消)、spec107(错误分支终止后静默)、spec108(取消后不再收到终止信号)、spec109(subscribe 抛非致命异常)、spec110(同一 Subscriber 重复订阅)、spec304(request 不做重计算)、spec305(cancel 不做重计算)。

另有 stochastic_spec103_mustSignalOnMethodsSequentially,重复检查信号是否串行。

SubscriberWhiteboxVerification

必须实现的方法

public abstract Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe);
public abstract T createElement(int element);          // 来自 WithHelperPublisher

publisherExecutorService() 在基类里已有默认实现:@BeforeClass 创建 4 线程池,@AfterClass 关闭。只有需要自定义线程模型时才覆写,且覆写后要自己负责关闭。

白盒没有 triggerRequest。测试通过 WhiteboxSubscriberProbe 注册的 SubscriberPuppet 控制请求与取消:

探针方法调用时机
registerOnSubscribe(SubscriberPuppet)收到 onSubscribe 后
registerOnNext(T)处理完 onNext 后
registerOnError(Throwable)处理完 onError 后
registerOnComplete()处理完 onComplete 后

SubscriberPuppet 只有两个方法:triggerRequest(long n) 和 signalCancel()。只实现 onNext 而不调用 registerOnNext,TCK 会认为信号从未到达,最终以超时失败。

14 个必测方法

方法规则
required_exerciseWhiteboxHappyPath综合路径
required_spec201_mustSignalDemandViaSubscriptionRequest§2.1
required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete§2.3
required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError§2.3
required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal§2.5
required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel§2.8
required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall§2.9
required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall§2.9
required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall§2.10
required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall§2.10
required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull§2.13
required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull§2.13
required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull§2.13
required_spec308_requestMustRegisterGivenNumberElementsToBeProduced§3.8

13 个 untested:spec202、spec204、spec206、spec207、spec211、spec212、spec213_failingOnSignalInvocation、spec301、spec310、spec311、spec314、spec315、spec316。

SubscriberBlackboxVerification

必须实现的方法

public abstract Subscriber<T> createSubscriber();
public abstract T createElement(int element);

publisherExecutorService() 同样有默认实现。黑盒额外提供:

public void triggerRequest(Subscriber<? super T> subscriber) {
    // 默认什么都不做
}

如果你的 Subscriber 不在 onSubscribe 里主动请求,而是等待外部触发,就必须覆写 triggerRequest;否则 TCK 发不出元素,用例会超时。

11 个必测方法

方法规则
required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest§2.1
required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete§2.3
required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError§2.3
required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal§2.5
required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall§2.9
required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall§2.9
required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall§2.10
required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall§2.10
required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull§2.13
required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull§2.13
required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull§2.13

15 个 untested:spec202、spec204、spec206、spec207、spec208、spec211、spec212、spec213_failingOnSignalInvocation、spec301、spec308、spec310、spec311、spec314、spec315、spec316。

IdentityProcessorVerification

必须实现的四个方法

public abstract Processor<T, T> createIdentityProcessor(int bufferSize);
public abstract Publisher<T> createFailedPublisher();
public abstract T createElement(int element);              // 来自 WithHelperPublisher
public abstract ExecutorService publisherExecutorService(); // 来自 WithHelperPublisher

createPublisher(long) 和 createSubscriber(probe) 都有默认实现:前者用 createHelperPublisher(elements) 生成元素再喂给 Processor,后者把同一个 Processor 包装成 Subscriber。不要自己再写一遍。

68 个用例的构成

组成数量说明
Publisher 侧 required22与 PublisherVerification 相同的 22 条
Subscriber 侧 required14与白盒相同的 14 条
Processor 专用 required2required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError、required_mustRequestFromUpstreamForElementsThatHaveBeenRequestedLongAgo
optional8与 Publisher 侧的 8 条一致
stochastic1spec103
untested20Publisher/Subscription 侧 7 条 + Subscriber 侧 13 条
§4.2 专用1mustImmediatelyPassOnOnErrorEventsReceivedFromItsUpstreamToItsDownstream

required_spec104_mustCallOnErrorOnAllItsSubscribers... 验证 Processor 遇到不可恢复错误时,所有下游都收到 onError;required_mustRequestFromUpstream... 验证「下游很早以前请求过的元素,Processor 必须向上游补请求」,这是 Processor 背压桥接的核心。

§4.1 没有单独的测试方法,它由 Publisher 与 Subscriber 两套委托用例共同覆盖。§4.2 的「不恢复就立即传播」由 mustImmediatelyPassOn... 专门验证;「恢复则必须把上游订阅视为已取消」在 1.0.4 中没有对应 required 用例。

Flow 版本

reactive-streams-tck-flow:1.0.4 提供 FlowPublisherVerification、FlowSubscriberWhiteboxVerification、FlowSubscriberBlackboxVerification 和 IdentityFlowProcessorVerification。抽象方法换成 Flow 类型:

基类必须实现
FlowPublisherVerification<T>createFlowPublisher(long)、createFailedFlowPublisher()
FlowSubscriberWhiteboxVerification<T>createFlowSubscriber(WhiteboxSubscriberProbe<T>)、createElement(int)
FlowSubscriberBlackboxVerification<T>createFlowSubscriber()、createElement(int);另有 triggerFlowRequest 钩子
IdentityFlowProcessorVerification<T>createIdentityFlowProcessor(int)、createFailedFlowPublisher()、createElement(int)、publisherExecutorService()

Reactor 3.8.8 的 Flux 实现的是 org.reactivestreams.Publisher,不是 java.util.concurrent.Flow.Publisher。要跑 Flow TCK,用 org.reactivestreams.FlowAdapters.toFlowPublisher(...) 适配,不要手写一层转换。

最小依赖与运行

<dependency>
    <groupId>org.reactivestreams</groupId>
    <artifactId>reactive-streams-tck</artifactId>
    <version>1.0.4</version>
    <scope>test</scope>
</dependency>

<!-- 只有验证 java.util.concurrent.Flow 类型时才需要 -->
<dependency>
    <groupId>org.reactivestreams</groupId>
    <artifactId>reactive-streams-tck-flow</artifactId>
    <version>1.0.4</version>
    <scope>test</scope>
</dependency>

一个可以直接跑通的 Publisher 测试:

import org.reactivestreams.Publisher;
import org.reactivestreams.tck.PublisherVerification;
import org.reactivestreams.tck.TestEnvironment;
import reactor.core.publisher.Flux;

public class RangePublisherTckTest extends PublisherVerification<Integer> {

    public RangePublisherTckTest() {
        super(new TestEnvironment(5_000));
    }

    @Override
    public Publisher<Integer> createPublisher(long elements) {
        return Flux.range(0, (int) elements);
    }

    @Override
    public Publisher<Integer> createFailedPublisher() {
        return null;   // 跳过失败路径用例;需要覆盖 §1.4 时换成 Flux.error(...)
    }
}

用 TestNG 运行:

java -cp "testng-7.3.0.jar:jcommander-1.78.jar:\
reactive-streams-tck-1.0.4.jar:reactive-streams-1.0.4.jar:\
reactor-core-3.8.8.jar:out" \
  org.testng.TestNG -testclass RangePublisherTckTest

Flow 版本只需替换基类和创建方法:

import org.reactivestreams.FlowAdapters;
import org.reactivestreams.tck.flow.FlowPublisherVerification;

public class FlowRangeTckTest extends FlowPublisherVerification<Integer> {

    public FlowRangeTckTest() {
        super(new TestEnvironment(5_000));
    }

    @Override
    public java.util.concurrent.Flow.Publisher<Integer> createFlowPublisher(long elements) {
        return FlowAdapters.toFlowPublisher(Flux.range(0, (int) elements));
    }

    @Override
    public java.util.concurrent.Flow.Publisher<Integer> createFailedFlowPublisher() {
        return null;
    }
}

实测计数

以下数字来自本机 TestNG 7.3.0 运行;createFailedPublisher() 返回 null,Publisher 两类用 1 秒超时:

基类被测实现总用例通过失败跳过
PublisherVerificationFlux.range382909
FlowPublisherVerificationFlux.range + FlowAdapters382909
SubscriberWhiteboxVerificationReactor BaseSubscriber2714013
SubscriberBlackboxVerificationReactor BaseSubscriber2611015
IdentityProcessorVerificationRxJava PublishProcessor68301424

Publisher 的 9 个跳过 = 7 个 untested_* + 2 个因没有失败 Publisher(optional_spec104、required_spec109_mayRejectCalls...)。把 createFailedPublisher() 改成 Flux.error(new RuntimeException("boom")) 后,变为 31 通过、7 跳过。

IdentityProcessorVerification 用热的多播处理器 PublishProcessor 时出现 14 个失败,不是 RxJava 不合规,而是「identity processor」要求能缓冲并按下游需求转发;热处理器没有下游时直接丢元素,语义不匹配。Processor 的 24 个跳过主要是 20 个 untested_* 和 2 个失败 Publisher 用例,其余是可选多播用例的条件跳过。

TestEnvironment 的默认超时是 100ms,不是 5000ms。单参构造器把默认超时、无信号超时、轮询间隔都设成同一个值;慢速 CI 上建议从 new TestEnvironment(5_000) 起步。

失败时怎么读

失败方法典型信息常见原因
required_createPublisher1MustProduceAStreamOfExactly1ElementExpected end-of-stream but got element [2]createPublisher(1) 多发了一个元素
required_spec309_requestZeroMustSignalIllegalArgumentExceptionExpected onError(java.lang.IllegalArgumentException) within 1000 msrequest(0) 被静默忽略
required_spec205_mustCallSubscriptionCancel...Expected 2nd Subscription given to subscriber to be cancelled, but Subscription.cancel() was not called within 5000 ms收到第二个 onSubscribe 时取消了旧订阅,而不是新订阅
required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling取消后仍有 onNextcancel() 只置标志,已排队任务继续发射
required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriberSubscriber 无法被 GCSubscription 取消后仍持有下游强引用

先看方法名里的规范编号,再对照上文的规则表;不要第一反应调大超时。超时通常说明信号根本发错了顺序或根本没发。

TCK 之外还要测什么

TCK 的 untested_* 已经列出一批空白,此外还有它天然覆盖不到的部分:

场景建议手段
长时间运行的内存泄漏JFR 分配采样、GC 日志、WeakReference + System.gc() 的弱引用断言
多线程竞争下的信号串行多轮压测 + 线程名/序号断言,而不是单次运行
业务语义(去重、聚合、顺序)普通单元测试与 StepVerifier
调度器与超时行为StepVerifier.withVirtualTime
取消后的资源释放连接、文件句柄、线程池的显式计数

一个补充 TCK 的 StepVerifier 例子,验证「逐个请求 + 中途取消」:

@Test
void requestOneByOneThenCancel() {
    StepVerifier.create(Flux.range(1, 5), 1)
        .expectNext(1)
        .thenRequest(1)
        .expectNext(2)
        .thenCancel()
        .verify();
}

再用一个手写 Subscriber 验证 SyncSinglePublisher 对 request(0) 的处理:

@Test
void nonPositiveRequestSignalsIllegalArgumentException() {
    AtomicReference<Throwable> error = new AtomicReference<>();

    new SyncSinglePublisher<>("x").subscribe(new Flow.Subscriber<>() {
        @Override public void onSubscribe(Flow.Subscription s) { s.request(0); }
        @Override public void onNext(String item) { fail("unexpected onNext"); }
        @Override public void onError(Throwable t) { error.set(t); }
        @Override public void onComplete() { fail("unexpected onComplete"); }
    });

    assertInstanceOf(IllegalArgumentException.class, error.get());
}

不要用 Runtime.totalMemory() - freeMemory() 判断泄漏:这个差值受分配速率和 GC 时机影响,既不是峰值也不是稳定占用。要证明「取消后 Subscriber 可回收」,用弱引用加 GC,或者直接用 JFR 看对象是否还被 Publisher 持有。

相关阅读

主题入口
Reactive Streams 规范reactive-streams.org
TCK 源码reactive-streams-jvm/tck
Project Reactor 文档projectreactor.io/docs
RxJava 文档github.com/ReactiveX/RxJava
背压规则清单附录 C
迁移指南附录 D

阅读导航

上一篇:附录 D 从 CompletableFuture 到响应式流迁移指南

下一篇:返回书稿书架 选择其他卷。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 15.3 全书总结与学习路径回顾
  2. 15.2 响应式流处理的演进趋势
  3. 15.1 引擎选型指南