2016-08-08 5 views
3

Wie implementiere ich die Generierung mehrerer Argumente mit FsCheck?Wie implementiere ich die Generierung mehrerer Argumente mit FsCheck?

I implementiert folgende mehrere Argument Generation zu unterstützen:

// Setup 
let pieces = Arb.generate<Piece> |> Gen.filter (isKing >> not) 
            |> Arb.fromGen 

let positionsList = Arb.generate<Space list> |> Arb.fromGen 

ich diese Argumente dann verwendet, um das Verhalten einer Funktion zu testen, die zur Erzeugung von Bewegung Optionen für einen bestimmten Prüfer verantwortlich ist:

// Test 
Prop.forAll pieces <| fun piece -> 
    Prop.forAll positionsList <| fun positionsItem -> 

     positionsItem |> optionsFor piece 
         |> List.length <= 2 

Verschachtelt Prop.forAll-Ausdrücke die richtige Technik beim Verwalten mehrerer generierter Argumenttypen?

Gibt es eine alternative Methode zum Generieren mehrerer Argumente für eine zu testende Funktion?

Hier ist die gesamte Funktion:

open FsCheck 
open FsCheck.Xunit 

[<Property(QuietOnSuccess = true)>] 
let ``options for soldier can never exceed 2``() = 

    // Setup 
    let pieces = Arb.generate<Piece> |> Gen.filter (isKing >> not) 
             |> Arb.fromGen 

    let positionsList = Arb.generate<Space list> |> Arb.fromGen 

    // Test 
    Prop.forAll pieces <| fun piece -> 
     Prop.forAll positionsList <| fun positionsItem -> 

      positionsItem |> optionsFor piece 
          |> List.length <= 2 

UPDATE

Hier ist die Lösung auf meine Frage, die von Mark Antwort:

[<Property(QuietOnSuccess = true, MaxTest=100)>] 
let ``options for soldier can never exceed 2``() = 

    // Setup 
    let pieceGen =  Arb.generate<Piece> |> Gen.filter (isKing >> not) 
    let positionsGen = Arb.generate<Space list> 

    // Test 
    (pieceGen , positionsGen) ||> Gen.map2 (fun x y -> x,y) 
           |> Arb.fromGen 
           |> Prop.forAll <| fun (piece , positions) -> 
                positions |> optionsFor piece 
                  |> List.length <= 2 

Antwort

6

Als allgemeine Beobachtung, Arbitrary Werte sind schwierig zu komponieren, während Gen Werte sind einfach. Aus diesem Grund neige ich dazu, meine FsCheck-Bausteine ​​in Bezug auf Gen<'a> anstelle von zu definieren.

Mit Gen Werte können Sie mehrere Argumente mit Gen.map2, Gen.map3, etcetera, oder Sie können verwenden, um die gen Berechnungsausdruck zusammenstellen.

Gen Bausteine ​​

Im OP Beispiel anstelle von pieces und positionsList als Arbitrary, definieren sie als Gen Werte definieren:

let genPieces = Arb.generate<Piece> |> Gen.filter (isKing >> not) 

let genPositionsList = Arb.generate<Space list> 

Diese Gen<Piece> 'Bausteine' der Typen sind und Gen<Space list>, jeweils.

Beachten Sie, dass ich sie genPieces statt einfach pieces nannte, und so weiter. Dies verhindert später Namenskollisionen (siehe unten). (Auch bin ich mir nicht sicher über die Verwendung des Plural s in , weil genPieces nur einen einzigen Piece Wert generiert, aber ich, da ich nicht Ihre gesamte Domäne weiß, entschied ich mich, wie es ist.)

Wenn Sie nur einen davon benötigen, können Sie ihn in einen Arbitrary mit Arb.fromGen konvertieren.

Wenn Sie sie erstellen müssen, können Sie eine der Kartenfunktionen oder Berechnungsausdrücke wie unten gezeigt verwenden. Dies gibt Ihnen eine Gen Tupel, und Sie können dann Arb.fromGen verwenden, um das in eine Arbitrary zu konvertieren.

Compose mit map2

Wenn Sie pieces und positionsList in einer Argumentliste zusammenzustellen, dann können Sie Gen.map2 verwenden:

Gen.map2 (fun x y -> x, y) genPieces genPositionList 
|> Arb.fromGen 
|> Prop.forAll <| fun (pieces, positionList) -> 
    // test goes here... 

Gen.map2 (fun x y -> x, y) gibt ein Zwei-Element-Tupel (ein Paar) von Werten, die Sie in der anonymen Funktion in (pieces, positionList) destrukturieren können.

sollte dieses Beispiel wird auch deutlich machen, warum genPieces und genPositionList bessere Namen für die Gen Werte sind: sie Raum verlassen den ‚nackten‘ Namen pieces und positionList für die generierten Werte an den Testkörper bestanden zu verwenden.

Compose Berechnung unter Verwendung des Ausdrucks

Eine weitere Alternative, die ich manchmal für komplexere Kombinationen bevorzugen ist, den Ausdruck gen Berechnung zu verwenden.

gen { 
    let! pieces = genPieces 
    let! positionList = genPositionList 
    return pieces, positionList } 
|> Arb.fromGen 
|> Prop.forAll <| fun (pieces, positionList) -> 
    // test goes here... 

Der anfängliche gen Ausdruck gibt auch ein Paar, so ist es mit Gen.map2 zu Zusammensetzung äquivalent:

Das obige Beispiel auch wie folgt geschrieben werden könnte.

Sie können die Option verwenden, die am besten lesbar ist.

Sie können weitere Beispiele von nicht-trivialen Gen Kombinationen in meinem Artikel Roman numerals via property-based TDD sehen.

+0

Dank Mark. Gibt es neben Blogs irgendwelche Bücher dazu? Ich suchte im Internet und konnte kein Nachschlagewerk finden. Daher würde ich gerne die Interna von Framework-basierten Test-Frameworks auf hoher Ebene verstehen, ohne ein FP-Guru zu sein. –

+0

@ScottNimrod Ich kenne keine Bücher. –

+0

[CEFP 2009 hat ein (bezahltes) Kapitel über QuickCheck, etwa 40 Seiten lang, geschrieben von John Hughes.] (Https://twitter.com/nikosbaxevanis/status/680704605819420672) –

Verwandte Themen