演習として、2 ~ 3 本のフィンガー ツリーを実装したいと考えました。これは、 FsCheckのモデルベースのテストを試す絶好の機会です。新しい実験版を試すことにしました。
これまでのところ、テスト マシン用のコマンドを 1 つだけコーディングしました。完全なコードはGitHubで入手できます。
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"
私が理解しているのは、単一の要素を持つ 1 つから 1 つOperation<_>.Run
を構築するために 2 回実行されるということです。ResizeArray
その後Operation<_>.Check
、同じ番号で 2 回実行され、1 つの要素に挿入されますFingerTree<_>
。
2 つのパスの最初のパス。単一要素ツリーを追加すると、最初のコマンドの後のモデルとよく比較される (正しい) 2 要素ツリーになります。
2 番目のコマンドは常に失敗します。Check
より大きなResizeList
(現在は 3 つの要素) で呼び出されますが、最初のコマンドと同じ単一要素の Tree です。もちろん、要素をもう 1 つ追加してもサイズ 3 にはならず、テストは失敗します。
Check
コマンドを実行するには、更新されたモデルを から返す必要があると予想していました。ただし、 a を返す必要があるProperty
ため、それは不可能です。
これにアプローチする方法を完全に誤解しましたか?動作するモデルベースのテストはどのように作成する必要がありますか?