2016-04-01 11 views
0

Ich definierte einen Typ mit ein paar benutzerdefinierten Generatoren, damit FsCheck benutzerdefinierte Instanzen von ein paar Typen generieren. Aber für einen der komplexen Typen möchte ich zuerst die Standard-FsCheck-Generierung verwenden und dann das Ergebnis anpassen. Hier ist eine (vereinfachte) Code:Aufruf Standard-FsCheck-Generator von einem benutzerdefinierten Generator des gleichen Typs

type CustomGenerators = 
    static member FirstCustomType() = /* function that returns FirstCustomType */ 
    static member SecondCustomType() = 
     Arb.generate<SecondCustomType> 
     |> Gen.map (fun x -> /* adjust some data in the generated instance */) 
     |> Arb.fromGen 

Das Problem ist, dass, wenn SecondCustomType() statische Methode nennt es Arb.generate ruft sofort SecondCustomType() verursacht eine endlose Rekursion. Ich verstehe, dass Arb.generate benutzerdefinierte Generatoren respektieren muss, deshalb ruft es statische SecondCustomType() auf, aber ich muss die standardmäßige (nicht angepasste) Arb.generate-Implementierung für SecondCustomType aufrufen. Ich kann die Implementierung nicht von einem anderen Typ aufrufen, da mein benutzerdefinierter Generator einen benutzerdefinierten Generator für FirstCustomType verwendet. Daher muss die SecondCustomType-Standardimplementierung alle benutzerdefinierten Generatoren kennen, die im Typ CustomGenerators definiert sind. Dies ist eine Art schlechter Kreis, für den ich noch keine saubere Lösung gefunden habe (nur Workaround).

+0

Ich würde vorschlagen, "SecondCustomType" in einem trivialen Wrapper nur für den Test, sagen "SecondCustomTypeTestWrapper", definieren Sie den benutzerdefinierten Generator für diesen Wrapper, nicht für "SecondCustomType" selbst, und Ihr Test nehmen Sie den Wrapper als Parameter. –

+0

Ja, das ist, was ich bereits getan habe, aber ich frage mich, ob es einen besseren Weg ohne zusätzlichen Typ Wrapper gibt. –

+0

Anstatt sofort Arbitratoren in statischen Klassen zu definieren, konnten Sie nicht einige normale F # -Funktionen definieren, die gen-Werte zurückgeben, und sie dann so zusammenstellen, wie Sie sie benötigen? Das ist, was ich normalerweise tue, aber dann verwende ich nie statische Convention-basierte Klassen ... –

Antwort

4

Alle "default" (d. H. Aus der Box) Generatoren sind in der FsCheck.Arb.Default Klasse. Abhängig davon, was Ihre SecondCustomType tatsächlich ist, können Sie einige Methoden für diese Klasse verwenden, z. B. Bool oder String.

Wenn Ihr Typ ein ordnungsgemäßer algebraischer F # -Typ ist (z. B. Vereinigung, Datensatz oder Tupel), können Sie den automatisch abgeleiteten Generator für solche Typen nutzen, der durch dargestellt wird.

type CustomGenerators = 
    static member SecondCustomType() = 
     Arb.Default.Derive<SecondCustomType>() 
     |> Arb.toGen 
     |> Gen.map (fun x -> (* adjust some data in the generated instance *)) 
     |> Arb.fromGen 

Having said that, würde ich oben mit Mark Kommentar einig: diese statisch-Methode-Shim-for-Typ-Klasse-Generatoren verwendet, wird immer ein bisschen umständlich. Genau wie Mark, ich bevorzuge es, wenn FsCheck bietet, was es aus der Box bieten kann, und dann die gewünschte Eingabe mit regulären Funktionen zusammenzustellen. Ich gebe dir ein Beispiel.

Betrachten Sie diese Art, die vermutlich nicht durch FsCheck aus dem Kasten heraus generiert werden:

type SomeAwkwardType(name: string, id: int, flag: bool) = 
    member this.Name = name 
    member this.Id = id 
    member this.Flag = flag 

Hier ist die unangenehme Art und Weise des statischen-Shim-für-Typ-Klasse Generator:

type AwkwardTypeGenerator() = 
    static member Gen() = 
     gen { 
     let! name = Arb.generate<string> 
     let! id = Arb.generate<int> 
     let! flag = Arb.generate<bool> 
     return SomeAwkwardType(name, id, flag) 
     } 

module Tests = 
    let [Property] ``Test using the awkward generator`` (input: SomeAwkwardType) = 
     someFn input = 42 

Und hier ist die einfacher (meiner Meinung nach) Weg, um die Eingabe zu erzeugen:

module Tests = 
    let [Property] ``Test using straightforward generation`` (name, id, flag) = 
     let input = SomeAwkwardType(name, id, flag) 
     someFn input = 42 

Dies ist nicht nur kürzer und sauberer, sondern hat auch den Vorteil, dass Sie Ihr Haar ein Jahr später nicht ziehen müssen, wenn Sie die Codebasis für die statische Klasse durchsuchen müssen, die den Generator implementiert.

+0

Danke Ich werde es später versuchen, wahrscheinlich morgen. –

+0

Es hat perfekt funktioniert! Arb.Default.Derive hat den Trick gemacht, also konnte ich die Anpassungen an den generierten Werten vornehmen, ohne die endlose Rekursion zu verursachen. Wenn es zu Ihrem anderen Vorschlag kommt (indem Sie einen geradlinigeren Weg zum Erzeugen von Eingaben verwenden), sieht es in diesem speziellen Test definitiv sauberer aus. Aber wenn der gewählte Ansatz das benutzerdefinierte SomeAckwardTypePropertyAttribute definiert, dann führt das Zurückkehren zu [Property] und das Senden an Testsätze mit einzelnen Werten anstelle der Instanz des Acward-Typs auch zu dessen Nachteilen. Also würde ich lieber Derive verwenden, wie Sie es vorgeschlagen haben. –