自定义 FsCheck 任意类型在 Xunit 中损坏但在 LINQPad 和常规 F# 程序中工作
Custom FsCheck Arbitrary type broken in Xunit but working in LINQPad and regular F# program
我正在尝试实现自定义 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
这是输出:
Falsifiable, after 2 tests (0 shrinks) (StdGen (1884571966,296370531)): Original: Pattern null with exception: System.NullReferenceException ...
这里是我在 LINQPad 中 运行ning 的代码以及输出:
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
Falsifiable, after 1 test (0 shrinks) (StdGen (1148389153,296370531)): Original: Pattern "a*"
如您所见,尽管我使用 Gen.elements
和 Gen.nonEmptyListOf
来控制测试数据,但 FsCheck 在 Xunit 测试中为 Pattern
生成了一个 null
值.此外,当我 运行 它几次时,我看到了超出指定字符范围的测试模式。在 LINQPad 中,这些模式是正确生成的。我还在 Visual Studio 2017 中使用常规 F# 控制台应用程序进行了相同的测试,自定义 Arbitrary
也按预期工作。
出了什么问题?当在 Xunit 中 运行ning 时,FsCheck 是否回退到默认值 string
Arbitrary
?
您可以克隆此存储库以亲自查看:https://github.com/bert2/GlobMatcher
(我不想使用 Prop.forAll
,因为每个测试都会有多个自定义 Arbitrary
,而 Prop.forAll
不太适合。据我所知知道我只能将它们组合起来,因为 Prop.forAll
的 F# 版本只接受单个 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)
(我部分是凭记忆写的,所以我可能犯了一些小的语法错误,但希望这能让你知道如何继续。)
另一方面,如果您选择使用 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)
综上所述,我不太喜欢'registering'这样的武断。我更喜欢使用组合器库,因为它是类型安全的,而整个基于类型的机制不是。
我正在尝试实现自定义 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
这是输出:
Falsifiable, after 2 tests (0 shrinks) (StdGen (1884571966,296370531)): Original: Pattern null with exception: System.NullReferenceException ...
这里是我在 LINQPad 中 运行ning 的代码以及输出:
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
Falsifiable, after 1 test (0 shrinks) (StdGen (1148389153,296370531)): Original: Pattern "a*"
如您所见,尽管我使用 Gen.elements
和 Gen.nonEmptyListOf
来控制测试数据,但 FsCheck 在 Xunit 测试中为 Pattern
生成了一个 null
值.此外,当我 运行 它几次时,我看到了超出指定字符范围的测试模式。在 LINQPad 中,这些模式是正确生成的。我还在 Visual Studio 2017 中使用常规 F# 控制台应用程序进行了相同的测试,自定义 Arbitrary
也按预期工作。
出了什么问题?当在 Xunit 中 运行ning 时,FsCheck 是否回退到默认值 string
Arbitrary
?
您可以克隆此存储库以亲自查看:https://github.com/bert2/GlobMatcher
(我不想使用 Prop.forAll
,因为每个测试都会有多个自定义 Arbitrary
,而 Prop.forAll
不太适合。据我所知知道我只能将它们组合起来,因为 Prop.forAll
的 F# 版本只接受单个 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)
(我部分是凭记忆写的,所以我可能犯了一些小的语法错误,但希望这能让你知道如何继续。)
另一方面,如果您选择使用 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)
综上所述,我不太喜欢'registering'这样的武断。我更喜欢使用组合器库,因为它是类型安全的,而整个基于类型的机制不是。