これは 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つのメリットとしてはおそらく
- Lawのtraitが簡単にインスタンス化しやすい(型を書く回数が減る)
ということだと思います(あまり自信ないので、後で調べてなにかほかに分かったら書きたい)。しかし一方デメリットとして
- 実行時には必要ないclassが入ってjarが大きくなってしまう
というのはあります。しかし、もとからすでにScalazのjarは大きい
2.9.2のscala-libraryのソースの行数が 79886行で、一方 scalaz 7.0.0-M4 の core の行数が 23663行 で3倍以上差があるのに scalaz のほうがバイナリサイズでかいってすごいな
2012-11-08 03:41:21 via web
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は、以下のようにいくつかありますが
- ScalaTest
- Scalacheck
- Specs2
- ScalaMock
- Specs
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日過ぎてもやります)