2016-04-01 45 views
0

我定義了一個包含少數自定義生成器的類型,以使FsCheck生成幾種類型的自定義實例。但對於其中一種複雜類型,我想先使用默認的FsCheck生成,然後調整結果。這裏有一個(簡化)代碼:從相同類型的自定義生成器中調用默認FsCheck生成器

type CustomGenerators = 
    static member FirstCustomType() = /* function that returns FirstCustomType */ 
    static member SecondCustomType() = 
     Arb.generate<SecondCustomType> 
     |> Gen.map (fun x -> /* adjust some data in the generated instance */) 
     |> Arb.fromGen 

的問題是,當SecondCustomType()靜態方法調用Arb.generate它立即調用SecondCustomType()引起無限遞歸。我知道Arb.generate必須尊重自定義生成器,所以這就是爲什麼它調用靜態SecondCustomType(),但我需要調用SecondCustomType的默認(不自定義)Arb.generate實現。由於我的自定義生成器使用FirstCustomType的自定義生成器,所以我無法調用其他類型的實現,因此默認的SecondCustomType實現必須知道CustomGenerators類型中定義的所有自定義生成器。這是一個壞圈,我還沒有找到一個乾淨的決議(只有解決方法)。

+0

爲了測試,我建議在''SecondCustomTypeTestWrapper''中爲''SecondCustomType''封裝一個簡單的包裝器,定義該包裝器的定製生成器,而不是'SecondCustomType'本身,並讓你的測試將包裝器作爲參數。 –

+0

是的,這是我已經做了,但我想知道沒有額外的類型包裝是否有更好的方法。 –

+0

而不是立即在靜態類中定義arbitraries,難道你不能定義一些返回gen值的正常F#函數,然後在你需要它們時編寫它們嗎?這就是我通常所做的,但是我從來沒有使用靜態的基於會話的類... –

回答

4

所有「默認」(即提供開箱即用)發電機在FsCheck.Arb.Default類。根據SecondCustomType的實際情況,您可以使用該類的一些方法,如BoolString

如果您的類型是適當的代數F#類型(即聯合,記錄或元組),您可以利用由Default.Derive表示的類型的自動派生生成器。

type CustomGenerators = 
    static member SecondCustomType() = 
     Arb.Default.Derive<SecondCustomType>() 
     |> Arb.toGen 
     |> Gen.map (fun x -> (* adjust some data in the generated instance *)) 
     |> Arb.fromGen 

話雖如此,我將與上述Mark的評論表示贊同:使用這些靜態方法,墊片換型級發電機總是會有點尷尬。就像Mark一樣,我更喜歡讓FsCheck提供開箱即用的功能,然後使用常規功能組合所需的輸入。我會給你一個例子。

考慮這種類型的,大概,不能被FsCheck產生開箱:

type SomeAwkwardType(name: string, id: int, flag: bool) = 
    member this.Name = name 
    member this.Id = id 
    member this.Flag = flag 

下面是使用靜態墊片換型類生成的尷尬方式:

type AwkwardTypeGenerator() = 
    static member Gen() = 
     gen { 
     let! name = Arb.generate<string> 
     let! id = Arb.generate<int> 
     let! flag = Arb.generate<bool> 
     return SomeAwkwardType(name, id, flag) 
     } 

module Tests = 
    let [Property] ``Test using the awkward generator`` (input: SomeAwkwardType) = 
     someFn input = 42 

這裏還有更直接的(在我看來)的方式來產生輸入:

module Tests = 
    let [Property] ``Test using straightforward generation`` (name, id, flag) = 
     let input = SomeAwkwardType(name, id, flag) 
     someFn input = 42 

這不僅更短,更清潔,而且還具有一年後不拉扯頭髮的優點,不必再爲實現生成器的靜態類查看整個代碼庫。

+0

謝謝,我會在稍後嘗試,可能明天。 –

+0

它工作完美! Arb.Default.Derive做了訣竅,所以我能夠對生成的值進行調整,而不會導致無限遞歸。當談到您的其他建議(使用更直接的方式來生成輸入)時,它在該特定測試中看起來更清晰。但是,當選擇的方法是定義自定義SomeAckwardTypePropertyAttribute,然後返回到[Property]併發送到測試集的單個值而不是acward類型實例也有其缺點。所以我寧願按照你的建議使用Derive。 –

相關問題