ReactiveStreamsのTCKを通すまでの手順(Publisher編)
ReactiveStreamsではSpecificationだけでなく、TCKも公開されています。
reactive-streams-jvm/tck at v1.0.0 · reactive-streams/reactive-streams-jvm · GitHub
前回公開したScalikeJDBC-streamsもこのTCKを通してみました。 その作業記録エントリです。
※Publisherのみです。
TL;DR
sbtでTestNGプラグインを導入する
ReactiveStreamsのJVM実装では、TCKがTestNGで用意されています。 まずは、sbtプロジェクトから、これを実行できるようにします。 TestNGプラグインが用意されているので、これを使います。
plugins.sbt
addSbtPlugin("de.johoop" % "sbt-testng-plugin" % "3.0.3")
build.sbt
import de.johoop.testngplugin.TestNGPlugin._ testNGSettings testNGVersion := "6.10" testNGSuites := Seq(((resourceDirectory in Test).value / "testng.xml").absolutePath)
sbt-testng 3.0.3時点のデフォルトで使用するTestNGのバージョンは6.9.13.6になっていますが、これだとレポートの出力時にエラーになることがあるので6.10を使うように指定しておきます。
また、TestNGではTest Suiteを外部ファイルで指定するようなのですが、デフォルトはtestng.yaml
になっていてDTDのサポートを受けられない点が逆に面倒だったのでXMLを使うよう変更しています。
TCKを書く
TCKもライブラリとして提供されているので、これをlibraryDependenciesに追加します。 バージョンは使用しているReactiveStreamsライブラリのバージョンと合わせておきます。
build.sbt
libraryDependencies += "org.reactivestreams" % "reactive-streams-tck" %"1.0.0" % "test"
テストコードは、ReactiveStreams TCKで提供されているPublisherVerificationを使用します。 TestNGはJavaのライブラリですが、Scalaでテストを書いていけます。例えば、こんな感じです。
import org.reactivestreams.Publisher import org.reactivestreams.tck.{ PublisherVerification, TestEnvironment } import org.testng.annotations.{ AfterClass, BeforeClass } class DatabasePublisherTckTest extends PublisherVerification[Any](DatabasePublisherTckTest.environment) { @BeforeClass def beforeClass(): Unit = { // ... } @AfterClass def afterClass(): Unit = { // ... } override def createPublisher(elements: Long): Publisher[Any] = { // ... } override def createFailedPublisher(): Publisher[Any] = { // ... } } object DatabasePublisherTckTest { val environment = new TestEnvironment(150L, false) // タイムアウトまでのmsの指定とデバッグ出力を有効にするかの指定 }
初期化や終了処理があればorg.testng.annotations.{ AfterClass, BeforeClass }
などを使用します。
PublisherVerificationで要求されるのは、createPublisher
とcreateFailedPublisher
の実装です。
createPublisher
引数elementsで要求された数の要素を持つPublisherを生成して返します。
この時、Long.MaxValue
が指定される場合がありますが、その場合は無限の要素を持つPublisherとして返す必要があります。
ScalikeJDBC-streamsでは有限のレコードのみをサポートするため、Long.MaxValue
が指定された場合はSkipExceptionを投げるようにしています。*1*2
createFailedPublisher
Spec Rule 1.9に則って、onSubscribeを呼び出した後のタイミングでonErrorを呼び出すPublisherを生成して返します。
テストする必要がなければ null
を返します。
注意)Specificationすべてがテストされるわけではない
Structure of the TCKにも書かれていますが、すべてのSpecificationを満たすテストが行われるわけではありません。 いくつかのSpecは意味のある自動テストを行うことが難しいようです。
テストケースの命名規則
ReactiveStreams TCKでは命名規則も定められています。
required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements
のように、テストの種類_対応するSpec番号_説明
とします。
上記のようにPublisherVerification
を使用する場合でも、テスト実行時のレポートを確認する際にこの命名規則を覚えておくと、どのSpecを満たしていないのか分かりやすいです。
TCKを実行する
testng.xml
は先のtestNGSuites
に設定したディレクトリの下に作成します。class
タグにテストしたいクラスを記述します。
ScalikeJDBC-streamsでは、こんな風に用意しました。
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > <suite name="ReactiveStreamsTCK" verbose="2" > <test name="DatabasePublisher"> <classes> <class name="scalikejdbc.streams.DatabasePublisherTckTest" /> </classes> </test> </suite>
これで、いつもどおりsbt test
でTestNGが実行されます。
TCKで提供される実装はTest Isolationであるためパラレルで実行してもOKのようです。
終わりに
ReactiveStreams TCKを利用すると簡単にSpecificationを満たしているか確認することができます。 ただ、テストはパスしているものの、出力されるログを見ながらこの実装で良いのだろうか…と再確認することもあったので、SpecificationとTCK実装の両方を随時確認しながら対応した方が良さそうでした。