2016-10-11 4 views
1

Als Übung wollte ich einen 2-3 Finger Baum implementieren. Das sollte die perfekte Gelegenheit sein, das modellbasierte Testen von FsCheck auszuprobieren. Ich entschied mich, das neuere experimental version zu versuchen.Kann nicht modellbasiert arbeiten Test

Bis jetzt habe ich nur einen Befehl für die Testmaschine programmiert, weil ich es bereits schaffe, diese Arbeit zu machen - andererseits hält sie den Beitrag kurz. Der vollständige Code ist unter GitHub verfügbar.

open CmdQ 
open Fuchu 
open FsCheck 
open FsCheck.Experimental 

type TestType = uint16 
type ModelType = ResizeArray<TestType> 
type SutType = FingerTree<TestType> 

let spec = 
    let prepend (what:TestType) = 
     { new Operation<SutType, ModelType>() with 
      override __.Run model = 
       // Also tried returning the same instance. 
       let copy = model |> ResizeArray 
       copy.Insert(0, what) 
       copy 

      override __.Check(sut, model) = 
       let sutList = sut |> Finger.toList 
       let newSut = sut |> Finger.prepend what 
       let newSutList = newSut |> Finger.toList 
       let modelList = model |> Seq.toList 
       let areEqual = newSutList = modelList 
       areEqual |@ sprintf "prepend: model = %A, actual = %A (incoming was %A)" modelList newSutList sutList 

      override __.ToString() = sprintf "prepend %A" what 
     } 

    let create (initial:ModelType) = 
     { new Setup<SutType, ModelType>() with 
      override __.Actual() = initial |> Finger.ofSeq 

      override __.Model() = initial //|> ResizeArray // Also tried this. 
     } 

    let rndNum() : Gen<TestType> = Arb.from<uint16> |> Arb.toGen 

    { new Machine<SutType, ModelType>() with 
     override __.Setup = 
      rndNum() 
      |> Gen.listOf 
      |> Gen.map ResizeArray 
      |> Gen.map create 
      |> Arb.fromGen 

     override __.Next _ = gen { 
      let! cmd = Gen.elements [prepend] 
      let! num = rndNum() 
      return cmd num 
     } 
    } 

[<Tests>] 
let test = 
    [spec] 
    |> List.map (StateMachine.toProperty >> testProperty "Finger tree") 
    |> testList "Model tests" 

Was ich verstehe, ist dies: Operation<_>.Run zweimal ausgeführt wird, ein ResizeArray von einem mit einem einzigen Element aufzubauen. Dann wird Operation<_>.Check zweimal mit den gleichen Nummern ausgeführt, um in ein einzelnes Element FingerTree<_> einzufügen.

Der erste der beiden Durchgänge. Single-Element-Tree-Eingang, Hinzufügen macht es zu einem (korrekten) Zwei-Elemente-Baum, der nach dem ersten Befehl gut mit dem Modell verglichen werden kann.

Der zweite Befehl ist immer der eine Fehler. Check wird mit dem größeren ResizeList (jetzt 3 Elemente) aber demselben Baum mit einem einzelnen Element wie im ersten Befehl aufgerufen. Das Hinzufügen eines weiteren Elements bringt es natürlich nicht auf Größe 3 und der Test schlägt fehl.

Ich hätte erwartet, dass ich das aktualisierte Modell von Check für die kommenden Befehle zurückgeben muss. Aber Sie müssen eine Property zurückgeben, so dass das nicht möglich ist.

Habe ich völlig falsch verstanden, wie ich das angehen soll? Wie sollte ein arbeitsmodellbasierter Test geschrieben werden?

Antwort

2

Die modellbasierte Tests geht davon aus, dass das „zu prüfende System“ als Nebenwirkung modifiziert wird, wenn Check auf einer bestimmten Operation genannt wird, und für diesen Testlauf initialisiert, wenn Setup.Actual() aufgerufen wird. Es ist für den Umgang mit veränderlichen Systemen gedacht - wie ein veränderbares Objekt - und dieser Stil, der hier etwas verwirrend ist, funktioniert mit solchen Systemen sehr gut.

type SutType = Ref<FingerTree<TestType>> 

und ändern Sie den Rest entsprechend:

Seit dem Finger Baumtyp unveränderlich ist, wäre mein Rat SutType zu neu zu definieren sein.

+0

Gut zu wissen! Wird das irgendwo in den Dokumenten erwähnt? Vielleicht sollte es prominenter sein. Vielen Dank. – primfaktor

+0

Deutlich nicht prominent genug, wenn Sie es verpasst haben :) –