FsCheck のプロパティを考えるのが難しい
-
20-09-2019 - |
質問
なんとか入手できました xユニット 私の小さなサンプルの組み立てに取り組んでいます。今、私は理解できるかどうかを確認したいです 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)
正しい解決策はありません
他のヒント
すでに具体的な答えがたくさんあるので、いくつかのアイデアを与える可能性のある一般的な答えをいくつか紹介します。
- 再帰関数の帰納的プロパティ。単純な関数の場合、これはおそらく再帰を再実装することになります。ただし、以下のようにシンプルにしてください。実際の実装は進化することが多いですが (例:末尾再帰的になる場合は、メモ化を追加します...) プロパティをわかりやすく保ちます。通常、ここでは ==> プロパティ コンビネータが役に立ちます。あなたのペア関数が良い例になるかもしれません。
- モジュールまたは型内の複数の関数を保持するプロパティ。これは通常、抽象データ型をチェックする場合に当てはまります。例えば:要素を配列に追加するということは、配列にその要素が含まれることを意味します。これにより、Array.add と Array.contains の一貫性がチェックされます。
- 往復:これはコンバージョンに適しています (例:解析、シリアル化) - 任意の表現を生成し、シリアル化、逆シリアル化して、元の表現と等しいかどうかを確認します。これは、splitOn と concat を使用して実行できる場合があります。
- 健全性チェックとしての一般プロパティ。可換性、結合性、冪等性 (何かを 2 回適用しても結果は変わらない)、再帰性など、一般に知られている、保持される可能性のあるプロパティを探します。ここでのアイデアは、関数を少し実行して、本当に奇妙な動作をするかどうかを確認することです。
一般的なアドバイスとして、あまり大げさなことはしないようにしてください。sndBigger の場合、適切なプロパティは次のとおりです。
sndが大きい場合にのみ「true」(a:int)(b:int)= sndbigger(a、b)= b> a
そしてそれはおそらくまさに実装です。心配する必要はありません。単純で昔ながらの単体テストが必要な場合もあります。罪悪感は必要ありません!:)
多分 このリンク (Pex チームによる) もいくつかのアイデアを提供しています。
から始めます 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 ただし、プロパティは引数として関数を取るため (!)、これを処理できます (したがって、「ランダム関数」を生成する必要があります)。これが機能しない場合は、手書きの関数を使用して、より具体的なプロパティをいくつか提供する必要があります。 f
. 。次に、次のチェックを実装します。 f
分割されたリスト内のすべての隣接するペアに対して true を返します ( kvb 示唆しています)実際にはそれほど難しいことではありません。
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
関数は、返される結果リストの一部として空のリストを返すことができます (その場合、最初/最後の要素が見つからないため)。
一部のコードの場合 (例: sndBigger
)、実装は非常に単純であるため、どのプロパティも少なくとも元のコードと同じくらい複雑になるため、FsCheck によるテストは意味をなさない可能性があります。ただし、他の 2 つの関数については、次の点を確認してください。
pairs
- 元の長さが 2 で割り切れない場合はどうなるでしょうか?例外のスローが正しい動作であるかどうかを確認できます。
List.map fst (pairs x) = evenEntries x
そしてList.map snd (pairs x) = oddEntries x
単純な機能の場合evenEntries
そしてoddEntries
あなたが書くことができます。
splitOn
- 関数がどのように動作するかについての説明を理解できれば、次のような条件を確認できます。「結果のすべてのリストについて」
splitOn f l
, 、f" および "リストの取得" を満たす 2 つの連続エントリはありません(l1,l2)
からsplitOn f l
ペアごとに、f (last l1) (first l2)
保持します」。残念ながら、ここでのロジックはおそらく実装自体の複雑さに匹敵します。
- 関数がどのように動作するかについての説明を理解できれば、次のような条件を確認できます。「結果のすべてのリストについて」