13

I've managed to get xUnit working on my little sample assembly. Now I want to see if I can grok FsCheck too. My problem is that I'm stumped when it comes to defining test properties for my functions.

Maybe I've just not got a good sample set of functions, but what would be good test properties for these functions, for example?

//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. 递归函数的归纳属性。对于简单的函数,这可能相当于重新实现递归。但是,请保持简单:虽然实际实现往往会发展(例如,它变成尾递归,您添加了记忆,...)保持属性简单。==> 属性组合器通常在这里派上用场。您的 pair 函数可能是一个很好的例子。
  2. 包含模块或类型中的多个函数的属性。检查抽象数据类型时通常会出现这种情况。例如:向数组中添加一个元素意味着该数组包含该元素。这将检查 Array.add 和 Array.contains 的一致性。
  3. 往返:这有利于转换(例如解析、序列化) - 生成任意表示、序列化、反序列化、检查它是否等于原始表示。您可以使用 splitOn 和 concat 来做到这一点。
  4. 作为健全性检查的一般属性。寻找可能具有的普遍已知的属性 - 诸如交换性、关联性、幂等性(应用两次不会改变结果)、自反性等。这里的想法更多是为了稍微练习一下这个函数 - 看看它是否真的有什么奇怪的事情.

作为一般建议,尽量不要把它搞得太大。对于 sndBigger,一个好的属性是:

让``当且仅当 snd 更大时才应该返回真`` (a:int) (b:int) = sndBigger (a,b) = b > a

这可能正是实现。不用担心——有时一个简单的老式单元测试正是您所需要的。无需内疚!:)

也许这个链接 (由 Pex 团队提供)也提供了一些想法。

于 2010-03-16T20:35:32.933 回答
9

I'll start with sndBigger - it is a very simple function, but you can write some properties that should hold about it. For example, what happens when you reverse the values in the tuple:

// 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

EDIT: This rule prop_sndBiggerSwap doesn't always hold (see comment by kvb). However the following should be correct:

// 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))

Regarding the pairs function, kvb already posted some good ideas. In addition, you could check that turning the transformed list back into a list of elements returns the original list (you'll need to handle the case when the input list is odd - depending on what the pairs function should do in this case):

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

For splitOn, we can test similar thing - if you concatenate all the returned lists, it should give the original list (this doesn't verify the splitting behavior, but it is a good thing to start with - it at least guarantees that no elements will be lost).

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

I'm not sure if FsCheck can handle this though (!) because the property takes a function as an argument (so it would need to generate "random functions"). If this doesn't work, you'll need to provide a couple of more specific properties with some handwritten function f. Next, implementing the check that f returns true for all adjacent pairs in the splitted lists (as kvb suggests) isn't actually that difficult:

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

Probably the only last thing that you could check is that f returns false when you give it the last element from one list and the first element from the next list. The following isn't fully complete, but it shows the way to go:

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

The last sample also shows that you should check whether the splitOn function can return an empty list as part of the returned list of results (because in that case, you couldn't find first/last element).

于 2010-03-15T14:15:03.023 回答
7

对于某些代码(例如sndBigger),实现是如此简单,以至于任何属性都至少与原始代码一样复杂,因此通过 FsCheck 进行测试可能没有意义。但是,对于其他两个功能,您可以检查以下内容:

  • pairs
    • 当原始长度不能被二整除时会发生什么?如果这是正确的行为,您可以检查是否引发异常。
    • List.map fst (pairs x) = evenEntries x以及您可以编写的List.map snd (pairs x) = oddEntries x简单功能。evenEntriesoddEntries
  • splitOn
    • 如果我理解您对函数应该如何工作的描述,那么您可以检查诸如“对于结果中的每个列表splitOn f l,没有两个连续的条目满足 f”和“(l1,l2)splitOn f l成对中获取列表,f (last l1) (first l2)保持”之类的条件。不幸的是,这里的逻辑在复杂性上可能与实现本身相当。
于 2010-03-15T13:47:06.013 回答