2016-12-06 3 views
1

Dans mon previous question Kurt m'a indiqué this code of FsCheck pour définir le type Arbitrary.Utilisation de FsCheck avec NUnit: réception d'une exception sur l'utilisation de types arbitraires (ou: comment utiliser des types arbitraires avec des attributs)

J'ai le suivant Arbitrary (avertissement: je n'ai aucune idée de ce que je fais ..., toujours trouver FsCheck notoirement difficile à comprendre, mais je suis prêt à le faire fonctionner), ce qui est en soi une simplification version de quelque chose que je créé précédemment:

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

et je l'utilise comme indiqué:

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

Cela me donne un (peu d'espoir) un message d'erreur qui me manque quelque chose:

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 

En repensant à ce code Github, je vois deux classes Atrbitrary mais pas d'héritage et elles ont toutes deux des membres statiques différents.

Comment puis-je créer un générateur de nombres aléatoires et l'assigner de manière arbitraire à mes tests NUnit?

Répondre

4

Le type que vous fournissez dans le paramètre Property.Arbitrary doit avoir membres statiques (éventuellement plusieurs) de type Arb. Comme dans le code lié:

type TestArbitrary2 = 
    static member NegativeDouble() = 
     Arb.Default.Float() 
     |> Arb.mapFilter (abs >> ((-) 0.0)) (fun t -> t <= 0.0) 

Appliqué à votre code, il devrait ressembler à ceci:

type MyArb() = 
    static member m() = Arb.Default.DoNotSizeInt64() 

La signification du paramètre Property.Arbitrary n'est pas « une mise en œuvre arbitraire » , mais plutôt "un seau d'implémentations typeclass".

Vous voyez, l'implémentation Haskell d'origine de QuickCheck repose sur typeclasses pour fournir des valeurs de différents types. Pour qu'un type particulier soit "quick-checkable", il doit y avoir une instance de la classe "Arbitrary" définie pour ce type (par exemple, here are instances for all basic types). Puisque F # ne supporte pas les classes de types en tant que telles, FsCheck doit faire semblant, et c'est le schéma utilisé ici: chaque instance de classe de type est représentée par un membre statique qui retourne la table de fonctions. Par exemple, si nous voulions simuler le Eq typeclass, nous aimerions le définir quelque chose comme ceci:

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 } 

Mais parce que vous ne pouvez pas numériser simplement tous les membres statiques dans toutes les assemblées chargées (ce serait un coût prohibitif), il y a ce petit inconvénient de fournir explicitement le type (en bonus, cela permet de contrôler la visibilité des "instances").

+0

si simple? Donc pas d'héritage, d'attributs etc? Essayer ... _ (edit: essayé, fonctionne instantanément, merci!) _ – Abel

+0

J'ai mis à jour la réponse avec quelques explications. –

+0

Merci pour l'explication supplémentaire, il est logique. Cependant, 'static member m()' n'est jamais appelé (j'ai essayé avec une instruction 'printfn' juste pour être sûr). Cela pourrait bien être un bug FsCheck (plus probablement mon bug;), mais comme écrit ci-dessus, il ne frappe jamais. – Abel

2

Cette question démontre clairement, IMO, pourquoi l'API basée sur la réflexion pour FsCheck est loin d'être idéale. J'ai tendance à éviter que l'API complètement, donc je place écrire la propriété OP comme ceci:

open FsCheck 
open FsCheck.Xunit 

[<Property>] 
let MultiplyIdentity() = 
    Arb.Default.DoNotSizeInt64() |> Prop.forAll <| fun (DoNotSize x) -> x * 1L = x 

Comme les open directives suggèrent, celui-ci utilise FsCheck.Xunit au lieu de FsCheck.NUnit, mais autant que je sache, il n'y a pas de différence dans la façon dont l'API fonctionne.

L'avantage de cette approche est qu'elle est de type sécurité et plus légère, car vous n'avez pas besoin d'implémenter des classes statiques chaque fois que vous devez modifier FsCheck.

+0

J'aime ça. Je l'ai juste essayé. J'avais peur que le test étant «unité», que la verbosité ne fonctionne pas (mais c'est le cas et je vois l'int64 aléatoire). Bien que je sois toujours curieux de savoir comment résoudre le problème original (Fyodor l'a rendu exécutable, mais la méthode n'est pas appelée), cela ressemble à une meilleure approche à partir de plusieurs perspectives, merci. – Abel

+0

En passant, et intéressant, seulement 5 des 100 tests utilisent un «int64» positif avec ce code, le repos est négatif. Cela vaut pour plusieurs courses. Est-ce que c'est par conception? (btw, problème original résolu, j'ai oublié de déballer, votre indice de type-aidé aidé) – Abel

+0

@Abel Maintenant que vous demandez, ce générateur ne, en effet, sembler qu'il est faussé. Je ne sais pas pourquoi, mais j'ai créé un problème à ce sujet: https://github.com/fscheck/FsCheck/issues/332 –

2

Si vous préférez the approach described by Mark Seemann, alors vous pouvez également envisager d'utiliser plaine FsCheck et se débarrasser de tout 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 sortie TestRunner:

------ 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>[email protected](String me.. 
    at <StartupCode$FsCheck>[email protected] 
    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). 
+0

J'avais effectivement un NUnit + FsCheck en cours d'exécution, mais je voulais que la combinaison avec 'Property' pour le rendre plus facilement configurable et pour obtenir une sortie plus significative dans mon coureur de test _ (OT: xUnit n'est pas utile pour mon scénario, il doesn ' L'écriture stdout et stderr prête à l'emploi, prétend que c'est une façon de multithreader (c'est), alors que c'est comme ça que je lance NUnit, avec stdout, avec multithreading, en utilisant NCrunch). – Abel

+0

Et un autre côté, l'approche de tuyauterie a eu un effet secondaire étrange dans le coureur de NUnit (seulement le coureur standard de NUnit, pas NCrunch): il signale qu'il exécute un test dans 0.12s, mais retourne seulement après 10+ secondes (ou plus , il commence à être visible à partir de 1000 ou alors FsCheck fonctionne avec la fonction la plus triviale). J'ai une idée de la cause, et cette cause est corrigée dans NUnit addin de FsCheck (et je n'ai pas trouvé un moyen de modeler cette correction dans la façon vanille que vous montrez ci-dessus avec xUnit). D'où l'exigence de vouloir utiliser 'PropertyAttribute'. – Abel