Persimmon用のランダムテストライブラリ案(もしくは scalacheck の F# 移植)

あけましておめでとうございます。

さっそくですが進捗どうですか? 私は駄目でした(Persimmon用VS Test Explorer拡張的な意味で)。

今回はランダムテスト実装の話です。

Persimmon と FsCheck の相性について

FsCheck には IRunner という、拡張ポイントがあります。 この型を実装して Config に設定することで、実装した各メソッドがコールバックされます。

が、これは このコメント にもある通り、xUnit.NET や NUnit ぽいものとは相性が良いものの、型を欲する Persimmon とは若干相性が良くないです。

では、 Runner だけ自力実装…というのも考えたのですが、断念しました。 実行に必要そうないくつかのモジュールや型が internal になっており、互換を崩してまであのエグい実装をコピーしてメンテしたいかというと、うーん…。

FsCheck の公開関数が unit を返す関数ばかりなのは何かの呪いですかね…。

そんなこんなで、FsCheck との連携は使いやすいかなんともいえないので、代替案のライブラリを作ってみようと思ったのが昨年末の話です。

干し柿(という名の scalacheck 移植)

というわけで、大晦日あたりから本命の息抜きに作ってみました。

persimmon-projects/Persimmon.Dried · GitHub

この干し柿は scalacheck をベースにしています。 手を入れるうちにいろいろ変わっていくと思うので、現時点では、という注釈つきですが。

FsCheck と scalacheck がどの程度異なるかは下記記事を参考にしてみてください。

FsCheck と ScalaCheck とを見比べる — a wandering wolf

以下、ちょっとした違いについて書いてみます。

Arbitrary

scalacheck の forAll などは型クラスを用いて Arbitrary インスタンスShrink インスタンスコンパイル時に補完します。 なので、別に ArbitraryGen しかもっていなくても不都合が起きません。 *1

しかし、この方法は正攻法で書いた場合の F# とは相性が悪いです。 そのため、 Persimmon.Dried では Arbitrary にシュリンカとプリティプリンタも持たせ、 forAll などには明示的に Arbitrary を適用する方式にしています。

プリティプリンタ

このあたりは scalacheck に似せています。 そういう理由もあって型に対応する出力を自分でカスタマイズ可能です。

型クラス

とはいえ、型クラスを全く使わないわけにもいきません。 なぜかというと、 Prop 型への変換は基本的に単純作業になりやすいからです。

そのため、 Prop を返す関数や演算子では基本的に PropApply という値を第1引数にとるに引数メソッド Instance を持つ第2引数の型に限って、inline 展開と型推論によって型を変換します。

現時点では

  • bool
  • PropResult
  • Prop
  • Persimmon.TestResult
  • Persimmon.AssertionResult

が変換対象です。

乱数生成

FsCheck は自前の、 scalacheck は標準のランダムモジュールを使っています。

Persimmon.Dried ではどうしようかなと迷ったのですが、この際なので FsRandom を使ってみることにしました。 既存かつ精度も申し分ないので問題ないと思っています。

実は単独でも動く

scalacheck ベースなので、当たり前といえば当たり前の話です。 ただ、 ConsoleReporter とコマンドラインパーサを(意図的に)実装していないので、スクリプトで実行しても何も出力されません。

この辺りも実装するかどうかは気分次第ですね。

おわりに

また一つ、オレオレライブラリが増えてしまった。

*1:Arbitrary 型がなくてもよいのでは、といわれることがあるのはこのためだと思っています