簡體   English   中英

FsCheck:覆蓋類型的生成器,但僅在單個父生成器的上下文中

[英]FsCheck: Override generator for a type, but only in the context of a single parent generator

我似乎經常遇到我想要生成一些復雜結構的情況,但是生成的成員類型的特殊變體不同。

例如,考慮這棵樹

type Tree<'LeafData,'INodeData> =
    | LeafNode of 'LeafData
    | InternalNode of 'INodeData * Tree<'LeafData,'INodeData> list

我想生成這樣的案例

  • 沒有內部節點是無子節點
  • 沒有葉型節點
  • 僅使用有限的葉子類型子集

如果我覆蓋相應子類型的所有代,這些很容易做到。 問題是似乎register本質上是一個線程級操作,並且沒有 gen-local 替代方案。

例如,我想要的可能看起來像

let limitedLeafs =
  gen {
    let leafGen = Arb.generate<LeafType> |> Gen.filter isAllowedLeaf
    do! registerContextualArb (leafGen |> Arb.fromGen)
    return! Arb.generate<Tree<NodeType, LeafType>>
  }

這個 Tree 示例特別可以解決一些創造性的類型改組,但這並不總是可行的。

也可以使用某種強制假設的遞歸映射,但如果上述可能的話,這似乎相對復雜。 不過,我可能會誤解 FsCheck 生成器的性質。

有誰知道如何完成這種 gen-local 覆蓋?

這里有幾個選項 - 我假設您使用的是 FsCheck 2.x,但繼續滾動查看 FsCheck 3 中的選項。

第一個是最自然的,但需要更多的工作,即明確地將生成器分解到您需要的級別,然后再次將 humpty dumpty 放在一起。 即不要太依賴基於類型的生成器派生 - 如果我正確理解你的示例,這將意味着實現遞歸生成器- 依賴Arb.generate<LeafType>泛型類型。

第二個選項 -Config有一個Arbitrary字段,您可以使用它來覆蓋Arbitrary實例。 即使被覆蓋的類型是自動生成的類型的一部分,這些覆蓋也會生效。 因此,作為草圖,您可以嘗試:

Check.One ({Config.Quick with Arbitrary = [| typeof<MyLeafArbitrary>) |]) (fun safeTree -> ...)

更廣泛的例子,它使用 FsCheck.Xunit 的PropertyAttribute但原理相同,而是在Config上設置。

最后的選擇! :) 在 FsCheck 3(預發行版)中,您可以通過一個新的(尚未記錄的)概念ArbMap來配置它,它使從類型到Arbitrary實例的映射顯式,而不是 2.x 中的這種靜態全局廢話(我當然不好。當時似乎是一個好主意。)實現在這里可能不會告訴你那么多 - 這個想法是你將一個ArbMap實例放在一起,其中包含子部分的“安全”生成器,然后你ArbMap.mergeWith使用ArbMap.defaults的安全地圖(從而在生成的ArbMap安全的生成器覆蓋默認生成器),然后將ArbMap.arbitraryArbMap.generate與生成的地圖一起使用。

很抱歉冗長的解釋——但總而言之,這應該給你兩全其美——你可以在 FsCheck 中重用通用聯合類型生成器,同時在該上下文中通過手術覆蓋某些類型。

FsCheck 對此的指導是:

要定義為現有類型生成正常值范圍的子集的生成器,例如所有偶數整數,如果您定義單例聯合案例並為新類型注冊生成器,它會使屬性更具可讀性:

例如,他們建議您可以像這樣定義任意偶數:

type EvenInt = EvenInt of int with
    static member op_Explicit(EvenInt i) = i

type ArbitraryModifiers =
    static member EvenInt() = 
        Arb.from<int> 
        |> Arb.filter (fun i -> i % 2 = 0) 
        |> Arb.convert EvenInt int
        
Arb.register<ArbitraryModifiers>() |> ignore

然后,您可以生成並測試其葉子為偶數的樹,如下所示:

let ``leaves are even`` (tree : Tree<EvenInt, string>) =
    let rec leaves = function
        | LeafNode leaf -> [leaf]
        | InternalNode (_, children) ->
            children |> List.collect leaves
    leaves tree
        |> Seq.forall (fun (EvenInt leaf) ->
            leaf % 2 = 0)

Check.Quick ``leaves are even``   // output: Ok, passed 100 tests.

老實說,我更喜歡您關於“本地覆蓋”的想法,但我認為 FsCheck 不支持它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM