ScalaCheck / QuickCheck を試す (2) [Scala]
前回 [1] の記事を書いた後 ScalaCheck の生成しているテストケースにちょっと疑問を感じたので引き続きいろいろ試してみたらどうにも理不尽な現象に気がついた。
まず「任意の整数は10より小さい」という馬鹿みたいな性質をテストしてみる。
scala> check(property((x:Int) => x < 10)) *** Failed, after 20 successful tests: The arguments that caused the failure was: List(20) res0: scalacheck.TestStats = TestStats(TestFailed(List(20)),20,0) scala> check(property((x:Int) => x < 10)) *** Failed, after 15 successful tests: The arguments that caused the failure was: List(13) res1: scalacheck.TestStats = TestStats(TestFailed(List(13)),15,0) scala> check(property((x:Int) => x < 10)) *** Failed, after 12 successful tests: The arguments that caused the failure was: List(11) res2: scalacheck.TestStats = TestStats(TestFailed(List(11)),12,0)
当然テストにパスしない。で、これを「任意の整数は100より小さい」に置き換えてみる。
scala> check(property((x:Int) => x < 100)) +++ OK, passed 100 tests. res3: scalacheck.TestStats = TestStats(TestPassed(),100,0) scala> check(property((x:Int) => x < 100)) +++ OK, passed 100 tests. res4: scalacheck.TestStats = TestStats(TestPassed(),100,0) scala> check(property((x:Int) => x < 100)) +++ OK, passed 100 tests. res5: scalacheck.TestStats = TestStats(TestPassed(),100,0)
馬鹿馬鹿しさ加減は変わらないのにこんどはテストにパスするようになってしまった。
最初これは ScalaCheck のバグかと思った(まだ 0.2 だし)のだが、本家の QuickCheck でも同様だった。
Hugs.Base> :load Test.QuickCheck Test.QuickCheck> quickCheck $ \x -> x < (10::Int) Falsifiable, after 31 tests: 14 Test.QuickCheck> quickCheck $ \x -> x < (10::Int) Falsifiable, after 50 tests: 21 Test.QuickCheck> quickCheck $ \x -> x < (10::Int) Falsifiable, after 36 tests: 11 Test.QuickCheck> quickCheck $ \x -> x < (10::Int) Falsifiable, after 23 tests: 12 Test.QuickCheck> quickCheck $ \x -> x < (10::Int) Falsifiable, after 24 tests: 14 Test.QuickCheck> quickCheck $ \x -> x < (10::Int) Falsifiable, after 31 tests: 10 Test.QuickCheck> quickCheck $ \x -> x < (10::Int) Falsifiable, after 32 tests: 18 Test.QuickCheck> quickCheck $ \x -> x < (10::Int) Falsifiable, after 59 tests: 23 Test.QuickCheck> quickCheck $ \x -> x < (10::Int) Falsifiable, after 18 tests: 11 Test.QuickCheck> quickCheck $ \x -> x < (10::Int) Falsifiable, after 19 tests: 10 Test.QuickCheck> quickCheck $ \x -> x < (35::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (35::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (35::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (35::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (35::Int) Falsifiable, after 99 tests: 37 Test.QuickCheck> quickCheck $ \x -> x < (35::Int) Falsifiable, after 95 tests: 38 Test.QuickCheck> quickCheck $ \x -> x < (35::Int) Falsifiable, after 84 tests: 38 Test.QuickCheck> quickCheck $ \x -> x < (35::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (35::Int) Falsifiable, after 87 tests: 44 Test.QuickCheck> quickCheck $ \x -> x < (35::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (100::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (100::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (100::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (100::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (100::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (100::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (100::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (100::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (100::Int) OK, passed 100 tests. Test.QuickCheck> quickCheck $ \x -> x < (100::Int) OK, passed 100 tests.
これって ScalaCheck/QuickCheck は Int の範囲の値から等確率で値を選び出すんじゃなくて0あたりから始めて最初のほうほど選ばれる確率が高いみたいな実装なのだろうか。もしそうだとしたらかなり不便というかこのままではまったく使えない気がするのだけど、こんな事実が知られていないわけでもないだろうから何か合理的な理由があってみんな納得して使っているのだろうか。それとも使い方を間違えているのかな。
[1] http://blog.so-net.ne.jp/rainyday/2007-08-08-1
こんにちは。ちょっと0.2のソース見てみたんですが(http://scalacheck.googlecode.com/svn/tags/0.2/src/scalacheck/Test.scala)、どうやら1引数バージョンのcheck(p: Prop)メソッドでは、
-100から100までの整数までの範囲の整数しか生成しない(しかも、
最初はもっと範囲が狭く、テストに成功するごとに範囲が広がっていく)
ような実装になっているようです。別バージョンである
check(prms: TestPrms, p: Prop)を使えば、生成する乱数の
範囲をカスタマイズできるようです。試しに
scala> check(TestPrms(100, 500, 1000), property((x:int) => x < 100))
scalacheck.TestStats = TestStats(TestFailed(List(123)),16,0)
という値が返り、テストが失敗したことが確認できました。
ただ、結局デフォルトの実装がこのようになっている理由はよくわかり
ませんが。
by みずしま (2007-08-12 00:44)
ちょっと訂正です。
誤:試しに
正:試しに以下のように、TestPrms型の値を第一引数として与えると
by みずしま (2007-08-12 00:54)
>-100から100まで
そうだったんですか。知らずに使うと結構な落とし穴ですね。ありがとうございます。
by ether (2007-08-12 12:39)
下記のリンクに書いてあることがこの問題に関係があるのかもしれません。
http://madscientist.jp/~ikegami/diary/20070823.html#p08
>QuickCheck/RushCheck では、「ランダムに入力を生成する、ただし、サイズの小さいものから順番に作る」というタクティクスを採用しています。(中略)小さな入力例でバグが見つかれば、原因をより突き止めやすくなります。
しかしこれは構造を持つデータについては当てはまりそうですが、整数を小さいほうから作る理由にはならないように思いました。
by ether (2007-08-23 21:18)