1

In my previous question Kurt pointed me to this code of FsCheck about setting the Arbitrary type.

I have the following Arbitrary (disclaimer: I have no idea what I am doing..., still finding FsCheck notoriously hard to understand but I'm dead set on getting it to work), which in itself is a simplified version of something I created earlier:

type MyArb() =
    inherit Arbitrary<DoNotSize<int64>>()
        override x.Generator = Arb.Default.DoNotSizeInt64().Generator

And I use it as instructed:

[<Property(Verbose = true, Arbitrary= [| typeof<MyArb> |])>]
static member  MultiplyIdentity (x: int64) = x * 1L = x

This gives me a (somewhat hopeful) error message that I'm missing something:

 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

Looking back at that Github code I see two Atrbitrary classes but neither with any inheritance and they both have different static members.

How can I create a random-number generator and assign it as an Arbitrary statically to my NUnit tests?

4

3 回答 3

4

您在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参数的含义不是“任意的实现”,而是“一桶类型类实现”。

你看,QuickCheck 的原始 Haskell 实现依赖于类型类来提供不同类型的值。为了使特定类型能够“快速检查”,需要为该类型定义一个“任意”类的实例(例如,这里是所有基本类型的实例)。

由于 F# 本身不支持类型类,因此 FsCheck 必须伪造它,这就是那里使用的方案:每个类型类实例都由一个返回函数表的静态成员表示。例如,如果我们想模拟Eqtypeclass,我们会这样定义它:

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 }

但是因为您不能只扫描所有加载的程序集中的所有静态成员(这将非常昂贵),所以显式提供类型存在一点不便(作为奖励,它允许控制“实例”的可见性)。

于 2016-12-06T16:50:37.913 回答
2

这个问题清楚地表明,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,但是 AFAIK,API 的工作方式没有区别。

这种方法的优点是它类型安全且更轻量级,因为您不必在每次需要调整 FsCheck 时都实现静态类。

于 2016-12-06T17:18:28.157 回答
2

如果您更喜欢Mark Seemann 描述的方法,那么您也可以考虑使用 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).
于 2016-12-08T04:00:44.023 回答