难以思考 FsCheck 的属性
-
20-09-2019 - |
题
我已经设法得到 x单位 正在组装我的小样品。现在我想看看我是否可以理解 安全检查 也。我的问题是,在为我的函数定义测试属性时,我感到很困惑。
也许我只是没有得到一组好的函数示例,但是例如,这些函数的良好测试属性是什么?
//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)
没有正确的解决方案
其他提示
已经有很多具体的答案,所以我将尝试给出一些一般性的答案,这可能会给您一些想法。
- 递归函数的归纳性质。对于简单的函数,这可能相当于重新实现递归。不过,保持简单:而实际的实施往往会不断发展(例如它变成尾递归,你添加记忆,...)保持属性简单。==> 属性组合器通常在这里派上用场。你的pairs函数可能就是一个很好的例子。
- 包含模块或类型中多个函数的属性。检查抽象数据类型时通常会出现这种情况。例如:向数组添加一个元素意味着该数组包含该元素。这会检查 Array.add 和 Array.contains 的一致性。
- 往返行程:这有利于转化(例如解析、序列化) - 生成任意表示,对其进行序列化、反序列化,检查它是否等于原始表示。您可以使用 splitOn 和 concat 来完成此操作。
- 作为健全性检查的一般属性。寻找可能成立的众所周知的属性——例如交换性、结合性、幂等性(应用两次不会改变结果)、自反性等。这里的想法更多的是稍微练习一下这个函数——看看它是否做了什么真正奇怪的事情。
作为一般性建议,尽量不要小题大做。对于 sndBigger 来说,一个好的属性是:
让``当且仅在snd更大时返回tru
这可能就是具体的实现。不要担心 - 有时一个简单的、老式的单元测试正是您所需要的。无需内疚!:)
或许 这个链接 (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
返回在分割后列出了所有相邻的一对真实的(如 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 进行测试可能没有意义。但是,对于其他两个函数,您可以检查以下一些内容:
pairs
- 当原始长度不能被二整除时,期望什么?您可以检查是否抛出异常,如果这是正确的行为。
List.map fst (pairs x) = evenEntries x
和List.map snd (pairs x) = oddEntries x
对于简单的功能evenEntries
和oddEntries
你可以写。
splitOn
- 如果我理解您对该函数应该如何工作的描述,那么您可以检查诸如“对于结果中的每个列表”之类的条件
splitOn f l
, ,没有两个连续条目满足 f" 和 "获取列表(l1,l2)
从splitOn f l
成对地,f (last l1) (first l2)
持有”。不幸的是,这里的逻辑在复杂性上可能与实现本身相当。
- 如果我理解您对该函数应该如何工作的描述,那么您可以检查诸如“对于结果中的每个列表”之类的条件