自定义FsCheck任意类型在Xunit中打破但在LINQPad和常规F#程序中工作

时间:2017-10-31 21:20:23

标签: f# xunit linqpad fscheck

我试图实现一个自定义Arbitrary来生成类似a*c?的glob语法模式。我认为我的实现是正确的,只是当用Xunit运行测试时,FsCheck似乎没有使用自定义任意Pattern来生成测试数据。当我使用LINQPad时,一切都按预期工作。这是代码:

open Xunit
open FsCheck

type Pattern = Pattern of string with
    static member op_Explicit(Pattern s) = s

type MyArbitraries =
    static member Pattern() = 
        (['a'..'c']@['?'; '*'])
        |> Gen.elements
        |> Gen.nonEmptyListOf 
        |> Gen.map (List.map string >> List.fold (+) "")
        |> Arb.fromGen
        |> Arb.convert Pattern string

Arb.register<MyArbitraries>() |> ignore

[<Fact>]
let test () = 
    let prop (Pattern p) = p.Length = 0
    Check.QuickThrowOnFailure prop

这是输出:

  

可验证,经过2次测试(0缩小)(StdGen(1884571966,296370531)):原始:Pattern null,异常:System.NullReferenceException ...

以下是我在LINQPad中运行的代码以及输出:

open FsCheck

type Pattern = Pattern of string with
    static member op_Explicit(Pattern s) = s

type MyArbitraries =
    static member Pattern() = 
        (['a'..'c']@['?'; '*'])
        |> Gen.elements
        |> Gen.nonEmptyListOf 
        |> Gen.map (List.map string >> List.fold (+) "")
        |> Arb.fromGen
        |> Arb.convert Pattern string

Arb.register<MyArbitraries>() |> ignore

let prop (Pattern p) = p.Length = 0
Check.Quick prop
  

可证伪,经过1次测试(0缩小)(StdGen(1148389153,296370531)):原文:Pattern&#34; a *&#34;

正如您所见,FsCheck在Xunit测试中为null生成Pattern值,尽管我使用Gen.elementsGen.nonEmptyListOf来控制测试数据。此外,当我运行几次时,我看到的测试模式超出了指定的字符范围。在LINQPad中,这些模式是正确生成的。我还在Visual Studio 2017中使用常规F#控制台应用程序测试了相同的内容,并且自定义Arbitrary也按预期工作。

出了什么问题?在Xunit中运行时,FsCheck是否会回退到默认string Arbitrary

您可以克隆此回购以自行查看:https://github.com/bert2/GlobMatcher

(我不想使用Prop.forAll,因为每项测试都会有多个自定义Arbitrary,而Prop.forAll并不适合。{目前为止据我所知,我只能对它们进行排序,因为Prop.forAll的F#版本只接受一个Arbitrary。)

1 个答案:

答案 0 :(得分:2)

不要使用Arb.register。此方法会改变全局状态,并且由于xUnit.net 2中内置的并行性支持,它在运行时未确定。

如果您不想使用FsCheck.Xunit胶水库,可以使用Prop.forAll,其工作方式如下:

[<Fact>]
let test () = 
    let prop (Pattern p) = p.Length = 0
    Check.QuickThrowOnFailure (Prop.forAll (MyArbitraries.Pattern()) prop)

(我在内存中写这部分内容,所以我可能会犯一些小的语法错误,但希望这可以让你知道如何继续。)

另一方面,如果您选择使用 FsCheck.Xunit ,则可以在Property注释中注册自定义Arbitraries,如下所示:

[<Property(Arbitrary = [|typeof<MyArbitraries>|])>]
let test (Pattern p) = p.Length = 0

正如您所看到的,这可以解决许多样板问题;你甚至不必打电话给Check.QuickThrowOnFailure

Arbitrary属性采用一系列类型,因此当您有多个类型时,这仍然有效。

如果需要使用相同的Arbitraries数组编写许多属性,则可以创建自己的自定义属性,这些属性派生自[<Property>]属性。 Here's an example

type Letters =
    static member Char() =
        Arb.Default.Char()
        |> Arb.filter (fun c -> 'A' <= c && c <= 'Z')

type DiamondPropertyAttribute() =
    inherit PropertyAttribute(
        Arbitrary = [| typeof<Letters> |],
        QuietOnSuccess = true)

[<DiamondProperty>]
let ``Diamond is non-empty`` (letter : char) =
    let actual = Diamond.make letter
    not (String.IsNullOrWhiteSpace actual)

所有这一切,我不太喜欢注册&#39;像这样的仲裁。我更喜欢使用组合器库,因为它是类型安全的,这种整个基于类型的机制都不是。