Goo*_*ide 6 f# xunit linqpad fscheck
我正在尝试实现一个自定义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
Run Code Online (Sandbox Code Playgroud)
这是输出:
可证伪,经过 2 次测试(0 次收缩)(StdGen (1884571966,296370531)):原始:Pattern null with exception: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
Run Code Online (Sandbox Code Playgroud)
可证伪,经过 1 次测试(0 次收缩)(StdGen (1148389153,296370531)):原始:模式“a*”
正如您所看到null的Pattern,尽管我正在使用Gen.elements和Gen.nonEmptyListOf来控制测试数据,但 FsCheck为Xunit 测试中的生成了一个值。此外,当我运行它几次时,我看到了超出指定字符范围的测试模式。在 LINQPad 中,这些模式是正确生成的。我还使用 Visual Studio 2017 中的常规 F# 控制台应用程序进行了相同的测试,并且自定义也Arbitrary按预期工作。
出了什么问题?string Arbitrary在 Xunit 中运行时 FsCheck是否回退到默认值?
你可以克隆这个 repo 自己看看:https : //github.com/bert2/GlobMatcher
(我不想使用Prop.forAll,因为每个测试都会有多个自定义Arbitrarys 并且Prop.forAll不能很好地进行。据我所知,我只能将它们组合起来,因为 F# 版本Prop.forAll只接受一个Arbitrary.)
不要使用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)
Run Code Online (Sandbox Code Playgroud)
(我部分是凭记忆写的,所以我可能犯了一些小语法错误,但希望这能让您了解如何继续。)
另一方面,如果您选择使用FsCheck.Xunit,则可以在Property注释中注册自定义任意值,如下所示:
[<Property(Arbitrary = [|typeof<MyArbitraries>|])>]
let test (Pattern p) = p.Length = 0
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,这解决了大部分样板文件;你甚至不必打电话Check.QuickThrowOnFailure。
该Arbitrary属性采用一系列类型,因此当您有多个类型时,这仍然有效。
如果您需要使用相同的 Arbitraries 数组编写许多属性,您可以创建派生自该[<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)
Run Code Online (Sandbox Code Playgroud)
话虽如此,我不太喜欢像这样“注册”任意对象。我更喜欢使用组合器库,因为它是类型安全的,而整个基于类型的机制则不是。