2015-01-05 43 views
2

我正試圖通過一個FsCheck的例子來研究一個帶有歧視的工會,以便爲我們的大型項目建立最佳實踐。現在我從我的發生器中得到空值,我不知道爲什麼。在以下代碼中,DataGen.containerGenerator爲null。FsCheck DataGen爲空

namespace Container 
open System 
open Xunit 
open FsCheck 

module ContainerLibrary = 
    type [<Measure>] oz 

    type Container = 
     | Cup of Common 
     | Bowl of Common 
    and Common = 
     { Volume :decimal<oz> 
      Weight :decimal} 

module DataGen = 
    type Generators = 
     static member arbVolume = 
      FsCheck.Gen.choose (1, 16) 
      |> FsCheck.Gen.map(fun x -> (decimal x/8.0M) * 1.0M<ContainerLibrary.oz>) 
      |> FsCheck.Arb.fromGen 

    FsCheck.Arb.register<Generators>() |> ignore 

    let bowlGenerator = 
     FsCheck.Gen.map2 (fun a b -> ContainerLibrary.Bowl({ Volume = a 
                   Weight = b})) 
         (Generators.arbVolume.Generator) 
         (FsCheck.Arb.generate<decimal>) 
    let cupGenerator = 
     FsCheck.Gen.map2 (fun a b -> ContainerLibrary.Cup({ Volume = a 
                  Weight = b})) 
         (Generators.arbVolume.Generator) 
         (FsCheck.Arb.generate<decimal>) 

    let containerGenerator = 
     Gen.oneof [bowlGenerator; cupGenerator] 

module Tests = 
    [<Fact;>] 
    let ``01 : Containers must be no more than 20 oz``() = 
     //Is this the best way to get one of something? 
     let c = FsCheck.Gen.sample 0 1 DataGen.containerGenerator |> Seq.head 
     Assert.NotNull (c) 
+0

將您的gen映射到NonNull 以避免null實例。 –

回答

2

它似乎並不當我運行它爲空,甚至當我得到更多的價值。您使用的是哪種版本的FsCheck?

[<Fact;>] 
let ``01 : Containers must be no more than 20 oz``() = 
    //Is this the best way to get one of something? 
    Gen.sample 0 100 DataGen.containerGenerator |> Seq.iter(fun c -> printf "%A" c; Assert.NotNull (c)) 

無論如何,有幾件事情需要注意你正在做的事情。

  • FsCheck使用反射來註冊生成器;並且測量類型參數的類型不能通過反射來看到。所以Arb.register實際上會覆蓋所有小數的十進制生成器。
  • 莫名其妙的FsCheck。你使用混淆智能感知的資格沒有結束。
  • Gen.sample是測試生成器的合理方式,但我主要在交互式設置中使用它;如果您經歷了設置測試的麻煩,我傾向於使用FsCheck內置的測試用例觀察功能。請參閱「觀察測試用例分佈」:https://fsharp.github.io/FsCheck/Properties.html
  • 根據F#中用於註冊生成器的模塊初始化規則,在模塊init中使用Arb.register有點脆弱。如果您使用的是Xunit,那麼使用內置集成來減少這方面的不可避免的挫折要好得多。

我已經重寫你的榜樣有點採取一些事情考慮:

module DataGen = 

open ContainerLibrary 

//can't really register this one because of the measure, would override all decimal generatos 
let volumeGenerator = 
     Gen.choose (1, 16) 
     |> Gen.map(fun x -> (decimal x/8.0M) * 1.0M<ContainerLibrary.oz>) 

let commonGenerator = 
    Gen.map2 (fun a b -> { Volume = a 
          Weight = b}) 
        (volumeGenerator) 
        (Arb.generate<decimal>) 

//in case you like applicative style, otherwise completely equivalent 
let commonGeneratorAlternative = 
    (fun a b -> { Volume = a; Weight = b}) <!> volumeGenerator <*> Arb.generate<decimal> 

let bowlGenerator = Gen.map Bowl commonGenerator 
let cupGenerator = Gen.map Cup commonGenerator 

let containerGenerator = 
    Gen.oneof [bowlGenerator; cupGenerator] 

type Generators = 
    static member Container() = containerGenerator |> Arb.fromGen 

module Tests = 
open FsCheck.Xunit 
open ContainerLibrary 

//use PropertyAttribute from FsCheck.Xunit 
//use the defined container generator - can also move this to module level 
//other ways to parametrize 
[<Property(Arbitrary=[|typeof<DataGen.Generators>|])>] 
//thanks to PropertyAttribute can now just take container as argument 
let ``01 : Containers must be no more than 20 oz`` (container:Container) = 
    match container with 
    | Cup common 
    | Bowl common -> common.Volume <= 20.0M<oz> 
    |> Prop.collect container //see the generated values in the output 

這個輸出是這樣的:

好了,通過100次測試。

1%Cup {Volume = 2.0M; 重量= -0.0000221360928858744815609M;}。 1%Cup {Volume = 1.8750M; Weight = 922337.20325598085121M;}。 等