6

我正在尝试实现一个Arbitrary生成 glob 语法模式的自定义,例如a*c?. 我认为我的实现是正确的,只是在使用 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)):原始:模式 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)):原始:模式“a*”

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

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

您可以克隆此 repo 以亲自查看:https ://github.com/bert2/GlobMatcher

(我不想使用Prop.forAll,因为每个测试都会有多个 customArbitrary并且Prop.forAll不适合。据我所知,我只能将它们组合起来,因为 F# 版本Prop.forAll只接受一个Arbitrary。)

4

1 回答 1

4

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

如果您不想使用FsCheck.Xunit Glue 库,可以使用Prop.forAll,它的工作原理如下:

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

(我部分是凭记忆写的,所以我可能犯了一些小的语法错误,但希望这能给你一个关于如何继续的想法。)


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

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

如您所见,这处理了大部分样板文件;你甚至不必打电话Check.QuickThrowOnFailure

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

如果您需要使用相同的任意数组编写许多属性,您可以创建自己的从属性派生的自定义[<Property>]属性。这是一个例子

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)

尽管如此,我不太喜欢像这样“注册”Arbitraries。我更喜欢使用组合器库,因为它是类型安全的,而整个基于类型的机制不是。

于 2017-11-01T06:42:42.357 回答