2015-01-05 3 views
2

Я пытаюсь работать с примером FsCheck прямо сейчас для типа с дискриминационными объединениями, чтобы установить лучшие практики для нашего более крупного проекта. Прямо сейчас я получаю null от своего генератора, и я не уверен, почему. В следующем коде DataGen.containerGenerator имеет значение null.FsCheck DataGen is null

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

Создайте свой ген для 'NonNull ', чтобы избежать пустых экземпляров. –

ответ

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. Квалификации, которые вы использовали, путают intellisense, нет конца.
  • Gen.sample - разумный способ тестирования генераторов, но я в основном использую его в интерактивных настройках; если у вас возникла проблема с настройкой теста, я, как правило, использую FsCheck, встроенный в возможности наблюдения за тестами. См. «Наблюдение распределения тестовых случаев» здесь: https://fsharp.github.io/FsCheck/Properties.html
  • Использование Arb.register в модуле init, как вы делаете, немного хрупкое, в зависимости от правил инициализации модуля в F # для регистрации генераторов. Если вы используете 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% Кубок {Volume = 2.0M; Вес = -0.0000221360928858744815609M;}. 1% Кубок {Объем = 1,8750М; Вес = 922337.20325598085121M;}. и т. Д.