将 FsCheck 与 NUnit 一起使用:在使用任意类型时接收异常(或:如何将任意类型与属性一起使用)
Using FsCheck with NUnit: receiving exception on using Arbitrary types (or: how to use Arbitrary types with attributes)
在我的 Kurt pointed me to this code of FsCheck中关于设置Arbitrary
类型。
我有以下 Arbitrary
(免责声明:我不知道我在做什么......,仍然发现 FsCheck 非常难以理解,但我已经下定决心让它工作),其中本身就是我之前创建的东西的简化版本:
type MyArb() =
inherit Arbitrary<DoNotSize<int64>>()
override x.Generator = Arb.Default.DoNotSizeInt64().Generator
我按照说明使用它:
[<Property(Verbose = true, Arbitrary= [| typeof<MyArb> |])>]
static member MultiplyIdentity (x: int64) = x * 1L = x
这给了我一个(有点希望的)错误消息,我错过了一些东西:
System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
----> System.Exception : No instances found on type Tests.Arithmetic.MyArb. Check that the type is public and has public static members with the right signature.
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at FsCheck.Runner.checkMethod(Config config, MethodInfo m, FSharpOption`1 target) in C:\Users\Kurt\Projects\FsCheck\FsCheck\src\FsCheck\Runner.fs:line 318
at FsCheck.NUnit.Addin.FsCheckTestMethod.runTestMethod(TestResult testResult) in C:\Users\Kurt\Projects\FsCheck\FsCheck\src\FsCheck.NUnit.Addin\FsCheckTestMethod.fs:line 100
回顾那个 Github 代码,我看到了两个 Atrbitrary
类,但它们都没有任何继承,并且它们都有不同的静态成员。
如何创建一个随机数生成器并将其作为任意静态分配给我的 NUnit 测试?
您在 Property.Arbitrary
参数中提供的类型应具有 类型 Arb
的静态成员(可能有多个)。如您链接的代码所示:
type TestArbitrary2 =
static member NegativeDouble() =
Arb.Default.Float()
|> Arb.mapFilter (abs >> ((-) 0.0)) (fun t -> t <= 0.0)
将此应用于您的代码,它应该如下所示:
type MyArb() =
static member m() = Arb.Default.DoNotSizeInt64()
Property.Arbitrary
参数的意思不是"an implementation of Arbitrary",而是"a bucket of typeclass implementations".
你看,QuickCheck 的原始 Haskell 实现依赖于 typeclasses 来提供不同类型的值。为了使特定类型成为 "quick-checkable",需要为该类型定义 'Arbitrary' class 的实例(例如,here are instances for all basic types)。
由于 F# 本身不支持类型 classes,因此 FsCheck 必须伪造它,这是那里使用的方案:每个类型 class 实例都由一个静态成员表示returns 函数 table。例如,如果我们想模拟 Eq
typeclass,我们会这样定义它:
type Eq<'a> = { eq: 'a -> 'a -> bool; neq: 'a -> 'a -> bool }
type EqInstances() =
static member ForInt() : Eq<int> =
{ eq = (=); neq = (<>) }
static member ForMyCustomType() : Eq<MyCustomType> =
{ eq = fun a b -> a.CompareTo(b) = 0
neq = fun a b -> a.CompareTo(b) <> 0 }
但是因为您不能只扫描所有已加载程序集中的所有静态成员(那会非常昂贵),显式提供类型会带来一些不便(作为奖励,它允许控制可见性"instances").
IMO,这个问题清楚地说明了为什么 FsCheck 基于反射的 API 不太理想。我倾向于完全避免 API,所以我会这样写 OP 属性:
open FsCheck
open FsCheck.Xunit
[<Property>]
let MultiplyIdentity () =
Arb.Default.DoNotSizeInt64 () |> Prop.forAll <| fun (DoNotSize x) -> x * 1L = x
正如 open
指令所建议的,这使用 FsCheck.Xunit 而不是 FsCheck.NUnit,但是据我所知,API 的工作方式没有区别。
这种方法的优点是它 类型安全 并且更轻量级,因为您不必在每次需要调整时都实现静态 类 FsCheck.
如果您更喜欢 ,那么您也可以考虑使用 plain-FsCheck 并完全摆脱 FsCheck.Xunit:
module Tests
open FsCheck
let [<Xunit.Fact>] ``Multiply Identity (passing)`` () =
Arb.Default.DoNotSizeInt64 ()
|> Prop.forAll
<| fun (DoNotSize x) ->
x * 1L = x
|> Check.QuickThrowOnFailure
let [<Xunit.Fact>] ``Multiply Identity (failing)`` () =
Arb.Default.DoNotSizeInt64 ()
|> Prop.forAll
<| fun (DoNotSize x) ->
x * 1L = -1L |@ sprintf "(%A should equal %A)" (x * 1L) x
|> Check.QuickThrowOnFailure
xUnit.net 测试运行器输出:
------ Test started: Assembly: Library1.dll ------
Test 'Tests.Multiply Identity (failing)' failed: System.Exception:
Falsifiable, after 1 test (2 shrinks) (StdGen (2100552947,296238694)):
Label of failing property: (0L should equal 0L)
Original:
DoNotSize -23143L
Shrunk:
DoNotSize 0L
at <StartupCode$FsCheck>.$Runner.get_throwingRunner@365-1.Invoke(String me..
at <StartupCode$FsCheck>.$Runner.get_throwingRunner@355.FsCheck-IRunner-On..
at FsCheck.Runner.check[a](Config config, a p)
at FsCheck.Check.QuickThrowOnFailure[Testable](Testable property)
C:\Users\Nikos\Desktop\Library1\Library1\Library1.fs(15,0): at Tests.Multi..
1 passed, 1 failed, 0 skipped, took 0.82 seconds (xUnit.net 2.1.0 build 3179).
在我的Arbitrary
类型。
我有以下 Arbitrary
(免责声明:我不知道我在做什么......,仍然发现 FsCheck 非常难以理解,但我已经下定决心让它工作),其中本身就是我之前创建的东西的简化版本:
type MyArb() =
inherit Arbitrary<DoNotSize<int64>>()
override x.Generator = Arb.Default.DoNotSizeInt64().Generator
我按照说明使用它:
[<Property(Verbose = true, Arbitrary= [| typeof<MyArb> |])>]
static member MultiplyIdentity (x: int64) = x * 1L = x
这给了我一个(有点希望的)错误消息,我错过了一些东西:
System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
----> System.Exception : No instances found on type Tests.Arithmetic.MyArb. Check that the type is public and has public static members with the right signature.
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at FsCheck.Runner.checkMethod(Config config, MethodInfo m, FSharpOption`1 target) in C:\Users\Kurt\Projects\FsCheck\FsCheck\src\FsCheck\Runner.fs:line 318
at FsCheck.NUnit.Addin.FsCheckTestMethod.runTestMethod(TestResult testResult) in C:\Users\Kurt\Projects\FsCheck\FsCheck\src\FsCheck.NUnit.Addin\FsCheckTestMethod.fs:line 100
回顾那个 Github 代码,我看到了两个 Atrbitrary
类,但它们都没有任何继承,并且它们都有不同的静态成员。
如何创建一个随机数生成器并将其作为任意静态分配给我的 NUnit 测试?
您在 Property.Arbitrary
参数中提供的类型应具有 类型 Arb
的静态成员(可能有多个)。如您链接的代码所示:
type TestArbitrary2 =
static member NegativeDouble() =
Arb.Default.Float()
|> Arb.mapFilter (abs >> ((-) 0.0)) (fun t -> t <= 0.0)
将此应用于您的代码,它应该如下所示:
type MyArb() =
static member m() = Arb.Default.DoNotSizeInt64()
Property.Arbitrary
参数的意思不是"an implementation of Arbitrary",而是"a bucket of typeclass implementations".
你看,QuickCheck 的原始 Haskell 实现依赖于 typeclasses 来提供不同类型的值。为了使特定类型成为 "quick-checkable",需要为该类型定义 'Arbitrary' class 的实例(例如,here are instances for all basic types)。
由于 F# 本身不支持类型 classes,因此 FsCheck 必须伪造它,这是那里使用的方案:每个类型 class 实例都由一个静态成员表示returns 函数 table。例如,如果我们想模拟 Eq
typeclass,我们会这样定义它:
type Eq<'a> = { eq: 'a -> 'a -> bool; neq: 'a -> 'a -> bool }
type EqInstances() =
static member ForInt() : Eq<int> =
{ eq = (=); neq = (<>) }
static member ForMyCustomType() : Eq<MyCustomType> =
{ eq = fun a b -> a.CompareTo(b) = 0
neq = fun a b -> a.CompareTo(b) <> 0 }
但是因为您不能只扫描所有已加载程序集中的所有静态成员(那会非常昂贵),显式提供类型会带来一些不便(作为奖励,它允许控制可见性"instances").
IMO,这个问题清楚地说明了为什么 FsCheck 基于反射的 API 不太理想。我倾向于完全避免 API,所以我会这样写 OP 属性:
open FsCheck
open FsCheck.Xunit
[<Property>]
let MultiplyIdentity () =
Arb.Default.DoNotSizeInt64 () |> Prop.forAll <| fun (DoNotSize x) -> x * 1L = x
正如 open
指令所建议的,这使用 FsCheck.Xunit 而不是 FsCheck.NUnit,但是据我所知,API 的工作方式没有区别。
这种方法的优点是它 类型安全 并且更轻量级,因为您不必在每次需要调整时都实现静态 类 FsCheck.
如果您更喜欢
module Tests
open FsCheck
let [<Xunit.Fact>] ``Multiply Identity (passing)`` () =
Arb.Default.DoNotSizeInt64 ()
|> Prop.forAll
<| fun (DoNotSize x) ->
x * 1L = x
|> Check.QuickThrowOnFailure
let [<Xunit.Fact>] ``Multiply Identity (failing)`` () =
Arb.Default.DoNotSizeInt64 ()
|> Prop.forAll
<| fun (DoNotSize x) ->
x * 1L = -1L |@ sprintf "(%A should equal %A)" (x * 1L) x
|> Check.QuickThrowOnFailure
xUnit.net 测试运行器输出:
------ Test started: Assembly: Library1.dll ------
Test 'Tests.Multiply Identity (failing)' failed: System.Exception:
Falsifiable, after 1 test (2 shrinks) (StdGen (2100552947,296238694)):
Label of failing property: (0L should equal 0L)
Original:
DoNotSize -23143L
Shrunk:
DoNotSize 0L
at <StartupCode$FsCheck>.$Runner.get_throwingRunner@365-1.Invoke(String me..
at <StartupCode$FsCheck>.$Runner.get_throwingRunner@355.FsCheck-IRunner-On..
at FsCheck.Runner.check[a](Config config, a p)
at FsCheck.Check.QuickThrowOnFailure[Testable](Testable property)
C:\Users\Nikos\Desktop\Library1\Library1\Library1.fs(15,0): at Tests.Multi..
1 passed, 1 failed, 0 skipped, took 0.82 seconds (xUnit.net 2.1.0 build 3179).