Using FsCheck to Generate Records

元气小坏坏 提交于 2019-12-11 10:57:39

问题


I'd like to use FsCheck (with XUnit) to create records of type: type QueryRequest = {Symbol: string; StartDate: DateTime; EndDate: DateTime} where Symbol is limited to 3 options - ORCL, IBM, AAPL, and StartDate and EndDate are limited to the range between January 1, 2000 and January 1, 2019.

However, I'm unclear as to how proceed. Should I use Arb.generate<T> or Arb.Default or some other utility upon which to base the generation and shrinking of the test cases?


Update 1

Follow-on question related to issues generating records is available here.

Original:
{ Symbol = ""
  StartDate = 8/9/2057 4:07:10 AM
  EndDate = 10/14/2013 6:15:32 PM }
Shrunk:
{ Symbol = ""
  StartDate = 8/9/2057 12:00:00 AM
  EndDate = 10/14/2013 12:00:00 AM }

Update 2

Following is test suite code:

namespace Parser

open Xunit
open FsCheck.Xunit
open DataGenerators

module Tests =
    [<Fact>]
    let ``sanity check`` () =
        let expected = true
        let actual = true
        Assert.Equal(expected, actual)

    [<Property(Arbitrary = [|typeof<StockTwitGenerator>|])>]
    let ``validate queries`` (q: QueryRecord) =
        q.EndDate > q.StartDate

回答1:


When you have constraints that limit the values to a small subset of all allowed values for a given type, constructing a valid value is easier and more safe1 than filtering.

Given...

open FsCheck
open System

type QueryRequest = {Symbol: string; StartDate: DateTime; EndDate: DateTime}

... we can start by creating a generator for Symbols:

let symbols = ["ORCL"; "IBM"; "AAPL"]
let symbol = Gen.elements symbols

and a date range

let minDate = DateTime(2000, 1, 1)
let maxDate = DateTime(2019, 1, 1)
let dateRange = maxDate - minDate
let date =
    Gen.choose (0, int dateRange.TotalDays)
    |> Gen.map (float >> minDate.AddDays)

Note that Gen.choose only accepts an int range. We can work around by generating a random offset of at max the allowed date difference and then mapping back to a DateTime

Using those, we can construct a generator for QueryRequests...

let query =
    gen {
        let! s = symbol
        let! d1 = date
        let! d2 = date
        let startDate, endDate = if d1 < d2 then d1, d2 else d2, d1 
        return { Symbol = s; StartDate = startDate; EndDate = endDate }
    }

type MyGenerators =
  static member QueryRequest() =
      {new Arbitrary<QueryRequest>() with
          override _.Generator = query }

... register ...

Arb.register<MyGenerators>()

and finally test:

let test { Symbol = s; StartDate = startDate; EndDate = endDate } =
    symbols |> Seq.contains s && startDate >= minDate && endDate <= maxDate && startDate <= endDate

Check.Quick test

1 FsCheck Documentation

Make sure there is a high chance that the predicate is satisfied.




回答2:


Arb.filter filters the generator and shrinker for a given Arbitrary instance to contain only those values that match with the given filter function. This should help you meet your needs.

https://fscheck.github.io/FsCheck/TestData.html#Useful-methods-on-the-Arb-module https://github.com/fscheck/FsCheck/blob/master/src/FsCheck/ArbitraryExtensions.fs#L17-17



来源:https://stackoverflow.com/questions/58615430/using-fscheck-to-generate-records

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!