2010-03-15 63 views
13

我設法得到了xUnit,它可以處理我的小樣本程序集。現在我想看看我能不能也FsCheck。我的問題是,當我爲我的函數定義測試屬性時,我很難過。對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) 

回答

7

對於一些代碼(例如sndBigger),實施是如此簡單,任何屬性將是至少一樣複雜的原代碼,所以通過FsCheck測試可能沒有意義。然而,對於其他兩個函數這裏有一些東西,你可以檢查:

  • pairs
    • 什麼期望時,原來的長度不被二整除?如果這是正確的行爲,你可以檢查是否拋出異常。
    • List.map fst (pairs x) = evenEntries xList.map snd (pairs x) = oddEntries x簡單函數evenEntriesoddEntries你可以寫。
  • splitOn
    • 如果我理解你的功能是如何工作的描述,那麼你可以檢查,如「對於在splitOn f l結果每一個列表中,沒有兩個連續的條目滿足F」的條件和「成對地從splitOn f l列表(l1,l2)f (last l1) (first l2)成立」。不幸的是,這裏的邏輯可能會與實現本身相當複雜。
+0

感謝您的提示。我已經在使用xUnit檢查異常,所以沒有增加值(據我所知?)。對於我來說,這一切似乎都是非常具有挑戰性的,正如你所說的,在這些特定情況下,測試的風險比原始測試更復雜。 – Benjol 2010-03-15 14:39:18

+3

檢查不應該更復雜 - 確實會影響測試的目的。但根據我的經驗,它們可能同樣複雜。隨着時間的推移,您的實現可能會變得更加複雜(例如優化),而屬性/規範通常保持大致相同。因此,雖然現在可能沒有意義,但您稍後可能會對這些屬性感到滿意。 – 2010-03-16 19:15:26

10

我會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已經張貼一些好的想法。另外,您可以檢查將變換後的列表變回到元素列表中,以返回原始列表(當輸入列表爲奇數時,您需要處理這種情況 - 這取決於在這種情況下函數應該做什麼):

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功能是否可以返回一個空列表作爲結果返回列表的一部分(因爲在那種情況下,你找不到第一個/最後一個元素)。

+0

謝謝,我將不得不嘗試一些這些想法。 – Benjol 2010-03-15 14:52:33

+0

偉大而徹底的答案。然而,你的'prop_sndBiggerSwap'並不總是成立,因爲'sndBigger(z,z)= sndBigger(swap(z,z))'對於所有'z'。 – kvb 2010-03-15 15:34:45

+0

@kvb:謝謝 - 你當然是對的!我編輯帖子以顯示規則的更正版本。 – 2010-03-15 16:12:15

14

已經有很多具體的答案,所以我會嘗試給出一些一般的答案,可能會給你一些想法。

  1. 遞歸函數的歸納性質。對於簡單的函數,這可能意味着重新實現遞歸。但是,保持簡單:實際的實現往往不會發展(例如,它變成尾遞歸,添加記憶,...)保持屬性直接。 ==>屬性組合器通常在這裏派上用場。您的配對功能可能是一個很好的例子。
  2. 容納模塊或類型中幾個函數的屬性。檢查抽象數據類型時通常是這種情況。例如:向數組添加元素意味着數組包含該元素。這將檢查Array.add和Array.contains的一致性。
  3. 往返:這對轉換很有用(例如解析,序列化) - 生成任意表示,序列化,反序列化,檢查它是否與原始序列相同。 您可以使用splitOn和concat做到這一點。
  4. 作爲健康檢查的一般屬性。尋找可能存在的一般已知屬性 - 諸如交換性,結合性,冪等性(應用兩次不會改變結果),反身性等等。這裏的想法更多的是鍛鍊功能 - 看它是否真的有點奇怪。

作爲一般性建議,儘量不要做太大的處理。對於sndBigger,一個好的屬性是:

讓``應該返回true當且僅當snd更大`(a:int)(b:int)= sndBigger(a,b)= b> a

而這可能正是實施。不要擔心 - 有時候一個簡單的老式單元測試就是你需要的。沒有罪惡感! :)

也許this link(由Pex團隊)也給出了一些想法。

+0

感謝您的提示。 – Benjol 2010-03-17 05:45:20

+0

給定的鏈接已死亡。更正的鏈接可能是http://research.microsoft.com/en-us/projects/pex/patterns.pdf – PetPaulsen 2011-02-25 11:46:02

+0

感謝 - 更新了鏈接。 – 2011-03-01 19:39:25