16

SmallCheckを使用して Haskell プログラムをテストしようとしていますが、ライブラリを使用して独自のデータ型をテストする方法がわかりません。どうやら、Test.SmallCheck.Seriesを使用する必要があります。ただし、そのドキュメントは非常に紛らわしいと思います。クックブック スタイルのソリューションと、論理 (モナディック?) 構造のわかりやすい説明の両方に興味があります。ここに私が持っているいくつかの質問があります(すべて関連しています):

  • データ型が の場合、有効な値が (または) であることをどのdata Person = SnowWhite | Dwarf Integerように説明すればよいですか? 複雑なデータ構造とコンストラクターがあり、コンストラクターを使用して Person のリストから FairyTale を作成したい場合はどうすればよいですか?smallCheckDwarf 1Dwarf 7SnowWhiteFairyTalemakeTale :: [Person] -> FairyTalesmallCheck

    のような関数へquickCheckの賢明なアプリケーションを使用することで、手を汚すことなくこのような作業を行うことができました 。これを行う方法がわかりませんでした(説明してください!)。Control.Monad.liftMmakeTalesmallCheck

  • Serialタイプ、などの関係は何Seriesですか?

  • (オプション) のポイントはcoSeries何ですか? Positiveからの型を使用するにはどうすればよいSmallCheck.Seriesですか?

  • (オプション) smallCheck のコンテキストで、モナド式であるべきものの背後にあるロジックとは何か、および単なる通常の関数とは何かについての説明をいただければ幸いです。

を使用するためのイントロ/チュートリアルがある場合はsmallCheck、リンクをいただければ幸いです。どうもありがとうございました!

更新:私が見つけた最も有用で読みやすいドキュメントsmallCheck、この論文 (PDF)であることを付け加えておきます。最初に見ただけでは、私の質問に対する答えが見つかりませんでした。チュートリアルというより説得力のある広告です。

更新 2:の種類やその他の場所にIdentity現れる奇妙なことについての質問を別の質問に移動しました。Test.SmallCheck.list

4

3 に答える 3

17

注:この回答は、SmallCheck の 1.0 より前のバージョンについて説明しています。SmallCheck 0.6 と 1.0 の重要な違いについては、このブログ投稿を参照してください。

SmallCheck は、可能な型の空間の一部でプロパティをテストするという点で QuickCheck に似ています。違いは、一連の小さな値の任意のサブセットではなく、すべての「小さな」値を網羅的に列挙しようとすることです。

ほのめかしたように、SmallCheckSerialは QuickCheck と似てArbitraryいます。

これSerialは非常に単純です:Serial型には、からの単なる関数である型を生成するa方法 ( ) があります。または、それを解き明かすために、オブジェクトは、いくつかの「小さな」値を列挙する方法を知っているオブジェクトです。また、生成する小さな値の数を制御するパラメーターも与えられますが、しばらく無視しましょう。seriesSeriesDepth -> [a]SerialDepth

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 を数値にマッピングして渡します。SeriesNNSerialSerialResultsSerialPerson


SerialインスタンスPersonができたので、それを使用して、より複雑なネストされたSerialインスタンスを構築できます。「インスタンス」の場合、FairyTaleが のリストである場合、インスタンスと一緒にインスタンスPersonを使用して、簡単に を作成できます。Serial a => Serial [a]Serial PersonSerial FairyTale

instance Serial FairyTale where
  series = map makeFairyTale . series
  coseries results = map (makeFairyTale .) . coseries results

(各関数が生成する(makeFairyTale .)composeは少し混乱します)makeFairyTalecoseries

于 2013-05-15T02:58:30.957 に答える
1

@telの回答は優れた説明だと思いますが(smallCheck実際に彼が説明したように機能したいと思います)、彼が提供するコードは私には機能しません(smallCheckバージョン1)。私は次のように動作させることができました...

更新/警告:以下のコードは、かなり微妙な理由で間違っています。修正版と詳細については、下記の質問に対するこの回答を参照してください。短いバージョンは、代わりにinstance Serial Identity Person書く必要があるということinstance (Monad m) => Series m Personです。

Control.Monad.Identity...しかし、すべてのコンパイラフラグの使用が奇妙であることがわかり、それについて別の質問をしました。

Series Person(または実際にはSeries Identity Person)実際には関数とまったく同じではありませんがDepth -> [Person](@telの回答を参照)、関数generate :: Depth -> [a] -> Series m aはそれらの間で変換されることにも注意してください。

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, FlexibleContexts, UndecidableInstances #-}
import Test.SmallCheck
import Test.SmallCheck.Series
import Control.Monad.Identity

data Person = SnowWhite | Dwarf Int

instance Serial Identity Person where
        series = generate (\d -> SnowWhite : take (d-1) (map Dwarf [1..7]))
于 2013-05-15T05:19:43.477 に答える