13

小さなサンプル アセンブリでxUnitを動作させることができました。ここで、 FsCheckも理解できるかどうかを確認したいと思います。私の問題は、関数のテスト プロパティを定義するときに困惑していることです。

関数の良いサンプル セットを持っていないだけかもしれませんが、たとえば、これらの関数の良いテスト プロパティは何でしょうか?

//transforms [1;2;3;4] into [(1,2);(3,4)]
pairs : 'a list -> ('a * 'a) list      //'

//splits list into list of lists when predicate returns 
//  true for adjacent elements
splitOn : ('a -> 'a -> bool) -> 'a list -> 'a list list

//returns true if snd is bigger
sndBigger : ('a * 'a) -> bool (requires comparison)
4

3 に答える 3

13

すでに多くの具体的な回答があるので、いくつかのアイデアを得ることができる一般的な回答を提供しようとします.

  1. 再帰関数の誘導特性。単純な関数の場合、これはおそらく再帰を再実装することになります。ただし、単純にしてください。実際の実装は頻繁に進化しますが (末尾再帰になる、メモ化を追加するなど)、プロパティを単純に保ちます。==> プロパティ コンビネータは通常、ここで役立ちます。あなたのペア関数は良い例になるかもしれません。
  2. モジュールまたは型の複数の関数を保持するプロパティ。これは通常、抽象データ型をチェックする場合に当てはまります。たとえば、配列に要素を追加するということは、配列にその要素が含まれていることを意味します。これにより、Array.add と Array.contains の一貫性がチェックされます。
  3. ラウンド トリップ: これは変換 (解析、シリアライゼーションなど) に適しています。任意の表現を生成し、シリアライズし、デシリアライズし、オリジナルと等しいことを確認します。これは、splitOn と concat を使用して実行できる場合があります。
  4. 健全性チェックとしての一般的なプロパティ。一般的に知られている、可換性、結合性、冪等性 (何かを 2 回適用しても結果は変わらない)、再帰性などの、保持される可能性のあるプロパティを探します。 .

一般的なアドバイスとして、あまり大きな取引をしないようにしてください。sndBigger の場合、適切なプロパティは次のようになります。

let ``snd が大きい場合のみ true を返す必要があります`` (a:int) (b:int) = sndBigger (a,b) = b > a

そして、それはおそらくまさに実装です。心配する必要はありません。シンプルで昔ながらの単体テストが必要な場合もあります。罪悪感不要!:)

おそらく、このリンク (Pex チームによる) もいくつかのアイデアを提供します。

于 2010-03-16T20:35:32.933 に答える
9

まず始めにsndBigger- これは非常に単純な関数ですが、保持するプロパティをいくつか書くことができます。たとえば、タプルの値を逆にするとどうなるか:

// Reversing values of the tuple negates the result
let swap (a, b) = (b, a)
let prop_sndBiggerSwap x = 
  sndBigger x = not (sndBigger (swap x))

// If two elements of the tuple are same, it should give 'false'
let prop_sndBiggerEq a = 
  sndBigger (a, a) = false

編集:このルールprop_sndBiggerSwapは常に成立するとは限りません ( kvbによるコメントを参照)。ただし、以下は正しいはずです。

// Reversing values of the tuple negates the result
let prop_sndBiggerSwap a b = 
  if a <> b then 
    let x = (a, b)
    sndBigger x = not (sndBigger (swap x))

pairs関数に関しては、 kvbがすでにいくつかの優れたアイデアを投稿しています。さらに、変換されたリストを要素のリストに戻すと、元のリストが返されることを確認できます (入力リストが奇数の場合を処理する必要があります -pairsこの場合に関数が何をすべきかによって異なります)。

let prop_pairsEq (x:_ list) = 
  if (x.Length%2 = 0) then
    x |> pairs |> List.collect (fun (a, b) -> [a; b]) = x
  else true

についてsplitOnは、同様のことをテストできます - 返されたすべてのリストを連結すると、元のリストが得られるはずです (これは分割動作を検証するものではありませんが、最初から始めるのは良いことです - 少なくとも、どの要素も失われます)。

let prop_splitOnEq f x = 
  x |> splitOn f |> List.concat = x

ただし、 FsCheckがこれを処理できるかどうかはわかりません(!)。これは、プロパティが関数を引数として取るためです (そのため、「ランダムな関数」を生成する必要があります)。これが機能しない場合は、いくつかの手書きの function を使用して、より具体的なプロパティをいくつか提供する必要がありますf次に、( kvbfが示唆するように) 分割されたリスト内のすべての隣接するペアに対して true を返すチェックを実装することは、実際にはそれほど難しくありません。

let prop_splitOnAdjacentTrue f x = 
  x |> splitOn f 
    |> List.forall (fun l -> 
         l |> Seq.pairwise 
           |> Seq.forall (fun (a, b) -> f a b))

おそらく最後に確認できる唯一のことは、あるリストの最後の要素と次のリストの最初の要素を指定するとf返されることです。false以下は完全ではありませんが、進むべき道を示しています。

let prop_splitOnOtherFalse f x = 
  x |> splitOn f
    |> Seq.pairwise 
    |> Seq.forall (fun (a, b) -> lastElement a = firstElement b)

最後のサンプルは、返される結果のリストの一部として関数が空のリストを返すことができるかどうかを確認する必要があることも示していますsplitOn(その場合、最初/最後の要素が見つからないため)。

于 2010-03-15T14:15:03.023 に答える
7

一部のコード (例: sndBigger) では、実装が非常に単純であるため、どのプロパティも少なくとも元のコードと同じくらい複雑になるため、FsCheck によるテストは意味をなさない場合があります。ただし、他の 2 つの機能については、次の点を確認してください。

  • pairs
    • 元の長さが 2 で割り切れない場合はどうなりますか? それが正しい動作である場合は、例外をスローするかどうかを確認できます。
    • List.map fst (pairs x) = evenEntries xおよびList.map snd (pairs x) = oddEntries x単純な関数evenEntriesoddEntries場合は、記述できます。
  • splitOn
    • 関数がどのように機能するかについての説明を理解できれば、「 の結果のすべてのリストについてsplitOn f l、2 つの連続するエントリが f を満たすことはない」や「ペアワイズ(l1,l2)からリストを取得すると、保持される」などの条件を確認できます。残念ながら、ここでのロジックの複雑さは、おそらく実装自体に匹敵します。splitOn f lf (last l1) (first l2)
于 2010-03-15T13:47:06.013 に答える