Scalaz におけるテストについて(その1)

これは Scalaz Advent Calendar 2012 の23日目です。


今年のアドベでも、去年、2011年の一人Scalaz Advent Calendarでも、テストについては誰も詳細には書いていなかったはずなので、テストについて書いていきます。eed3si9nさんの、独習Scalazの4日目には*1ある程度でてきていました。具体的には

  • Functor
  • Applicative
  • Semigroup
  • Monoid

のLawについてです。上記のもの以外にもScalaz7内部にはまだ多くあるし、自分でも完全に把握してなかったので、まとめてみたのが2日前に書いたこれです

https://gist.github.com/4356594

現時点で全部で21ありました。typeclassのLawを定義出来る場合は、それぞれのtypeclassに対してひとつずつLawのtraitが定義されてます。
ただ、MonadPlusだけは例外で、MonadLawとMonadPlusLawという2つのLawが定義されてます。MonadPlusに関してのみ2つLawが定義してある理由は詳しく知りません。あとで調べて書くかもしれません。もしくは詳しい人いたら教えて下さい。

また、すべてのtypeclassにLawが定義されているわけではありません。ここにtypeclassの一覧がありますが

https://github.com/scalaz/scalaz/blob/v7.0.0-M6/project/GenTypeClass.scala#L19-L71

Lawが定義されているのは、半分もないとおもいます。Lawが定義されてないのは、そもそも定義できないか、定義してもほとんど意味が無いからでしょう。


そもそもLawとはなんなのか?」を説明せずに、ScalazのLawやtypeclassの一覧の話をしてきましたが、Law自体は「typeclassが満たすべき法則」です。この法則を理解することはそれなりに重要なので、テストする人以外もまぁ読んでおいたほうがいいと思います。英語で説明されているのではなく、コードで書いてあるので、ある意味わかりやすいとおもいます。そして、特徴的なのが

テストコードではなくメインのコードの内部に埋め込んでいる点

です。この仕組みはScalaz6にはなかったし、自分が知ってる限りHaskellでも同じようなことはしていないはずです。

なぜメインのほうに埋め込んでいるのか?という点ですが、その1つのメリットとしてはおそらく

ということだと思います(あまり自信ないので、後で調べてなにかほかに分かったら書きたい)。しかし一方デメリットとして

  • 実行時には必要ないclassが入ってjarが大きくなってしまう

というのはあります。しかし、もとからすでにScalazのjarは大きい

ので、Lawのtraitだけ気にしても仕方ないです。


Lawの説明をしてきましたが、Law自体は単に満たすべき法則が定義されてるだけで、これだけあってもテストできません。Scalazでは、これらのLawとScalacheckを組み合わせてテストをするために、専用のscalacheck-bindingというモジュールがあります。

https://github.com/scalaz/scalaz/tree/v7.0.0-M6/scalacheck-binding/src/main/scala/scalaz/scalacheck

scalacheck-bindingを説明するまえに、簡単にScalacheckの話もしておきます。

Scalaにおけるテストのためのライブラリ*2は、以下のようにいくつかありますが

Scalazが主に使っているのが、Scalacheck(と一部Specs2)です。ググればいっぱいでてくるだろうし、それぞれのテストライブラリの詳細な説明は省きますが、Scalacheckについては、一言で言うと

HaskellのQuickCheckというライブラリのScala版」です。

Scalacheckの説明はこれくらいにして、scalacheck-bindingの説明に戻ります。scalacheck-bindingには、現状ファイルは3つしかありません。



1つ目は ScalazArbitrary.scala です。
ArbitraryとはScalacheckのライブラリのclassです。Arbitraryは、テストのための値を自動生成するためのものです。ScalazArbitrary.scalaには、Scalaz独自のデータ構造に対するそれぞれのArbitraryが定義されています。



2つ目が ScalazProperties.scalaです。ここでやっと最初に話したtypeclassのLawが関係してきます。

"それぞれのtypeclassのLawのメソッドを、Arbitraryから自動生成した値を渡すことによって、まとめて呼び出すための仕組み"

がここで定義されています。*3



3つ目の ScalaCheckBinding.scala は、他に比べればかなりコード数は少なく、それほど使う機会はないかもしれません。*4 scalacheckのclassであるArbitraryやGenに対するMonadインスタンスなどが定義されます。



これらの説明を順にしていったうえで、やっと実際のテストの説明に入ることができるわけですが、長くなったので、また続きは別の日に書きます(アドベもう終わるけど、まぁ25日過ぎてもやります)

*1:ほかにもLensのところとか、いくつかでてくる

*2:これ以外にも、Javaのものもそのまま使えます

*3:ちなみに、このファイル内のコードは、わりと書き方の法則が決まっていて単純作業になってしまっているので、もうちょっと工夫して短く書けないかなーと思っていたりします

*4:ScalazArbitraryが使っていますが