如何使用FsCheck生成器生成两个相同类型的记录,其中一个记录的属性与另一个记录的属性不同

时间:2017-06-12 05:55:59

标签: f# nunit fscheck

我有这个fscheck nunit测试生成两条记录然后我必须更新,以便两条记录总是具有不同的Direction属性值

[<Property( Verbose = true )>]
let ``calculate Net Worth 2`` (first:Bill,second:Bill) =
  let owing = { first with Direction = Out }
  let payCheck = { second with Direction = In }

  let compositeBill = {
    Bills = [| owing; payCheck |] 
  }
  let netWorth = calculateNetWorth compositeBill
  Assert.AreEqual(payCheck.Amount - owing.Amount,netWorth)

我不想手动设置Direction = In或Direction = In,我想使用生成器来指定它。

这样的发电机怎么样?

我希望留下像这样的代码

[<Property( Verbose = true )>]
let ``calculate Net Worth 2`` (first:Bill,second:Bill) =
  let compositeBill = {
    Bills = [| owing; payCheck |] 
  }
  let netWorth = calculateNetWorth compositeBill
  Assert.AreEqual(payCheck.Amount - owing.Amount,netWorth)

这是我没有运气的尝试

type BillsGen =
    static member Bill () =        
        let debit = 
          Arb.generate<Bill>
          |> Gen.map (fun dt -> { dt with Direction = Out} )          
        let credit = 
          Arb.generate<Bill>
          |> Gen.map (fun dt -> { dt with Direction = In} )
        Gen.oneof[ debit; credit ]        

[<SetUp>]
let setup () =
    do Arb.register<BillsGen>() |> ignore 

谢谢

以下是我的一些类型

   type Direction = 
    | In  
    | Out

   type Bill = {
     Direction : Direction
   }

   type CompositeBill = {
    Bills : Bill [] 
   }

2 个答案:

答案 0 :(得分:2)

我没有基于属性的测试经验,但我认为你会通过创建一个代表你受约束的输入值的新类型来做到这一点。然后,您可以在生成器中使用Gen.zip来组合两个生成器来构建该类型的值。

type BillsInOut = BillsInOut of Bill * Bill

type BillsInOutGen =
    static member BillsInOut () =
        { new Arbitrary<BillsInOut>() with
            override x.Generator =
                let credit =
                    Arb.generate<Bill>
                    |> Gen.map (fun dt -> { dt with Direction = In })
                let debit =
                    Arb.generate<Bill>
                    |> Gen.map (fun dt -> { dt with Direction = Out })

                Gen.zip credit debit |> Gen.map BillsInOut }

一旦你运行Arb.register<BillsInOutGen>(),你就可以把这个新类型作为你的测试参数,这个属性应该成立:

let property (BillsInOut (inBill, outBill)) =
    inBill.Direction = In && outBill.Direction = Out

编辑:另一种方法

我刚刚想到,由于这两个账单是独立的,所以它们可以单独生成,因此不需要将生成器压缩在一起,您可以根据需要获得不同的类型:

type BillIn = BillIn of Bill
type BillOut = BillOut of Bill

type BillInOutGen =
    static member BillIn () =
        { new Arbitrary<BillIn>() with
            override x.Generator =
                Arb.generate<Bill> |> Gen.map (fun dt -> BillIn { dt with Direction = In }) }
    static member BillOut () =
        { new Arbitrary<BillOut>() with
            override x.Generator =
                Arb.generate<Bill> |> Gen.map (fun dt -> BillOut { dt with Direction = Out }) }

Arb.register<BillInOutGen>()

现在使用这些的属性如下所示:

let property (BillIn inBill) (BillOut outBill) =
    inBill.Direction = In && outBill.Direction = Out

答案 1 :(得分:1)

@thequickbrownfox的回答非常有帮助。但是我必须在测试中做一些改变。我不得不创建Bill * Bill的BillsInOut = BillsInOut类型,并指定类来保存我的测试,从那里一切都工作了 解决方案如下所示

type BillsInOut = BillsInOut of Bill * Bill

type BillsInOutGen =
  static member BillsInOut () =
    { 
     new Arbitrary<BillsInOut>() with
      override x.Generator =
        let credit =
          Arb.generate<Bill>
          |> Gen.map (fun dt -> { dt with Direction = In })
        let debit =
          Arb.generate<Bill>
          |> Gen.map (fun dt -> { dt with Direction = Out })        
        Gen.zip credit debit |> Gen.map BillsInOut 
    }


[]
type ``when analysing bills``() =

  [<SetUp>]
  member x.SetUp() = 
   Arb.register<BillsInOutGen>() |> ignore

  [<Property( Verbose = true )>]
  member x.``it should calculate net worth`` (BillsInOut (payCheck, owing)) = 
    Assert.True(payCheck.Direction = In && owing.Direction = Out)       

单独生成两个账单的解决方案并没有很好地解决问题