注:この回答は、SmallCheck の 1.0 より前のバージョンについて説明しています。SmallCheck 0.6 と 1.0 の重要な違いについては、このブログ投稿を参照してください。
SmallCheck は、可能な型の空間の一部でプロパティをテストするという点で QuickCheck に似ています。違いは、一連の小さな値の任意のサブセットではなく、すべての「小さな」値を網羅的に列挙しようとすることです。
ほのめかしたように、SmallCheckSerial
は QuickCheck と似てArbitrary
います。
これSerial
は非常に単純です:Serial
型には、からの単なる関数である型を生成するa
方法 ( ) があります。または、それを解き明かすために、オブジェクトは、いくつかの「小さな」値を列挙する方法を知っているオブジェクトです。また、生成する小さな値の数を制御するパラメーターも与えられますが、しばらく無視しましょう。series
Series
Depth -> [a]
Serial
Depth
instance Serial Bool where series _ = [False, True]
instance Serial Char where series _ = "abcdefghijklmnopqrstuvwxyz"
instance Serial a => Serial (Maybe a) where
series d = Nothing : map Just (series d)
このような場合、パラメータを無視して、Depth
各タイプの「すべての」可能な値を列挙するだけです。一部のタイプでは、これを自動的に行うこともできます
instance (Enum a, Bounded a) => Serial a where series _ = [minBound .. maxBound]
これは、プロパティを徹底的にテストするための非常に簡単な方法です。文字どおり、考えられるすべての入力をテストします。ただし、明らかに少なくとも 2 つの大きな落とし穴があります。(1) 無限のデータ型は、テスト時に無限ループにつながります。(2) ネストされた型は、参照する例のスペースが指数関数的に大きくなります。どちらの場合も、SmallCheck は非常に急速に大きくなります。
これがパラメーターのポイントです。Depth
これにより、システムは私たちをSeries
小さく保つように要求できます。ドキュメントからDepth
、
生成されたテスト値の最大深度
データ値の場合、ネストされたコンストラクター アプリケーションの深さです。
機能値の場合、ネストされたケース分析の深さと結果の深さの両方です。
では、例を作り直して、小さいままにしましょう。
instance Serial Bool where
series 0 = []
series 1 = [False]
series _ = [False, True]
instance Serial Char where
series d = take d "abcdefghijklmnopqrstuvwxyz"
instance Serial a => Serial (Maybe a) where
-- we shrink d by one since we're adding Nothing
series d = Nothing : map Just (series (d-1))
instance (Enum a, Bounded a) => Serial a where series d = take d [minBound .. maxBound]
ずっといい。
それで、何coseries
ですか?QuickCheckの型クラスと同様coarbitrary
に、一連の「小さな」関数を作成できます。Arbitrary
入力型の上にインスタンスを書き込んでいることに注意してください---結果の型は別のSerial
引数で渡されます (下で を呼び出していますresults
)。
instance Serial Bool where
coseries results d = [\cond -> if cond then r1 else r2 |
r1 <- results d
r2 <- results d]
これらを記述するにはもう少し創意工夫が必要です。実際に、alts
以下で簡単に説明するメソッドを使用する方法を紹介します。
Series
では、どうすればいくつかのsを作ることができるでしょうPerson
か? この部分は簡単です
instance Series Person where
series d = SnowWhite : take (d-1) (map Dwarf [1..7])
...
しかし、私たちの関数は、 s から何か他のものへのcoseries
すべての可能な関数を生成する必要があります。Person
これはaltsN
、SmallCheck が提供する一連の関数を使用して実行できます。ここにそれを書く1つの方法があります
coseries results d = [\person ->
case person of
SnowWhite -> f 0
Dwarf n -> f n
| f <- alts1 results d ]
基本的な考え方は、インスタンスを持つ値からのインスタンスへの関数をaltsN results
生成することです。したがって、それを使用して、以前に定義された値である [0..7] から必要なものへの関数を作成し、s を数値にマッピングして渡します。Series
N
N
Serial
Serial
Results
Serial
Person
のSerial
インスタンスPerson
ができたので、それを使用して、より複雑なネストされたSerial
インスタンスを構築できます。「インスタンス」の場合、FairyTale
が のリストである場合、インスタンスと一緒にインスタンスPerson
を使用して、簡単に を作成できます。Serial a => Serial [a]
Serial Person
Serial FairyTale
instance Serial FairyTale where
series = map makeFairyTale . series
coseries results = map (makeFairyTale .) . coseries results
(各関数が生成する(makeFairyTale .)
composeは少し混乱します)makeFairyTale
coseries