はじめに
ScalaCheckについて少し書こうと思います。
まず、なぜ今回ScalaCheckについて書こうと思ったのかですが、これは#7 市ヶ谷Geek★Night「Scala大名の平成維新〜殿中でScala!〜」でQiita:@mtoyoshiさんの発表やtwitter:@gakuzzzzさんの発表によるところが大きいです。
- http://www.slideshare.net/MasakiToyoshima/ss-61358550
- http://gakuzzzz.github.io/slides/logic_or_types_as_constraint_short/#1
どちらの発表も型による制約を取り扱っていて、がくぞさんの資料にはScalaCheckを使ったサンプルコードがあります。
このへんの話を自分なりに整理するために書きます。イメージとしては豊島さんの資料でScalaCheckを使ったらどうなるかという感じで整理しようと思います。
次の2つのケースについてサンプルコードを用意します。
- 型で制約を表現しないケース
- 型で制約を表現したケース
制約と一言で言ってしまうと雲をつかむような話になってしまいがちですが、親しみのある道路の信号機(交通信号機)を使ったものを用意しました。多分、通じると思います。
物語としては自動車を運転する話です。信号が青ならばRun、赤ならばStop、黄ならば運転手の性格によってRunだったりStopになる。そういう話です。
まずは型による制約がないほうから紹介します。
型で制約を表現しないケース
ちょっと雑かもしれませんが、サンプルコードは下記URL先になります。
今回話したいところはdef drive(red: Option[Signal], yellow: Option[Signal], blue: Option[Signal]): DrivingState
なので、それ以外は飾りです。
信号は赤、黄、青の3色です。これをOption型で表現しています。
テストにはそれを反映して、3種のテストデータだけが生成されるようになっています。
def genOptionSignal = Gen.oneOf((Some(Signal), None, None), (None, Some(Signal), None), (None, None, Some(Signal)))
sbtのconsoleで(None, None, None)のようなケースを試すと、次のような例外になります。
Type in expressions for evaluation. Or try :help.
scala> val person = new Person(Gentle)
person: Person = Person@648c416d
scala> val car = new Car(person)
car: Car = Car@4fa6918f
scala> car.drive(None, None, None)
scala.MatchError: (None,None,None) (of class scala.Tuple3)
at Car.drive(Car.scala:3)
... 42 elided
scala>
つまり、ありえないケースでは例外になります。
sbtのtestを試すと、次のような警告が表示されます。
[warn] It would fail on the following inputs: (None, None, None), (None, Some(_), Some(_)), (Some(_), None, Some(_)), (Some(_), Some(_), None), (Some(_), Some(_), Some(_))
[warn] val result = signal match {
[warn] ^
[warn] one warning found
[info] + Car.constraint: OK, passed 100 tests.
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success] Total time: 3 s, completed 2016/12/24 20:18:27
親切な警告です。「次の入力で失敗するでしょう」という警告です。そうです失敗します。
これは型による制約が必要であることの手がかりになります。この警告を見たら、型による制約を検討することをおすすめします。
次に、どこをどう修正すれば型による制約を取り入れることができるかを紹介します。
型で制約を表現するケース
こちらもちょっと雑ですが、差分は下記URL先になります。
sbtのtestを試しても、警告は表示されません。
ScalaCheckのコツ
最後にScalaCheckのコツについて少し書きます。
ScalaCheckのサンプルコードをみると、chooseを使ったものが多いのですが、個人的にはoneOfが馴染みやすいと思います。あと、for式を使うと型クラスと仲良くなれます。
詳しくはScalaCheck Documentationを参照していただくのがよいと思います。
ScalaCheckを使ったことがないようであれば、APIを確認するためにソースコードを参照することをお勧めします。API Documentationからソースコードを参照するのがよいと思います。ちなみに、API Documentationは更新されていないようでchooseなどが記載されていません(私の確認の仕方が悪いのかもしれませんが)。
IntelliJを使っているようであれば、直接ソースコードを参照するのが良いと思います。
テストの話ですが、ScalaCheckを使うと入力に何が来るのかがわからないのでテストケースにテスト対象のメソッドの実装を書くことになったりします。そういう場合はScalaCheckを使うべきではないかもしれません。
ScalaCheckはシリアライズとデシリアライズ、エンコードとデコードのようなペアーの処理に対して有効なようです。
まとめ
自分で確かめてみることが一番確実で一番役に立つので、手を動かして確認していただけると幸いです。